From b4b3c046536b1d764b61bc5e39dd9915887029d0 Mon Sep 17 00:00:00 2001 From: Eli Boyarski Date: Sun, 11 Sep 2016 18:27:39 +0300 Subject: [PATCH 001/388] Remove dead code from __init__.py --- mock/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/mock/__init__.py b/mock/__init__.py index 82a31103..8f383f0e 100644 --- a/mock/__init__.py +++ b/mock/__init__.py @@ -2,6 +2,3 @@ import mock.mock as _mock from mock.mock import * __all__ = _mock.__all__ -#import mock.mock as _mock -#for name in dir(_mock): -# globals()[name] = getattr(_mock, name) From b7a77db8b4d4ae34101b2de810f41c2371c2170e Mon Sep 17 00:00:00 2001 From: Roy Williams Date: Thu, 22 Sep 2016 16:34:30 -0700 Subject: [PATCH 002/388] I am investigating a migration to Python 3, and to facilitate this we are using the -3 flag as decribed here: https://docs.python.org/3/howto/pyporting.html#prevent-compatibility-regressions . When using this flag I encountered some issues inside of mock itself. Python 3 now requires you to implement __hash__ if you implement __eq__. See https://docs.python.org/3.6/reference/datamodel.html#object.%5F%5Fhash%5F%5F . ```python {mock.ANY} # Fine in Python 2, Throws in Python 3 ``` This PR explicitly sets the `__hash__` method on these objects as `None` to ensure the behavior is consistent in Python 3 as well as Python 2. --- mock/mock.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mock/mock.py b/mock/mock.py index c674a858..0239ce7b 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2057,6 +2057,8 @@ def __ne__(self, other): def __repr__(self): return '' + __hash__ = None + ANY = _ANY() @@ -2199,6 +2201,7 @@ def __eq__(self, other): def __ne__(self, other): return not self.__eq__(other) + __hash__ = None def __call__(self, *args, **kwargs): if self.name is None: From 027fb0b5ee95587dc2780727d9c22125441c7c49 Mon Sep 17 00:00:00 2001 From: Eli Boyarski Date: Sun, 11 Sep 2016 17:41:58 +0300 Subject: [PATCH 003/388] Update version in header to 2.0.0 --- mock/mock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock/mock.py b/mock/mock.py index 0239ce7b..f246f2a3 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2,7 +2,7 @@ # Test tools for mocking and patching. # E-mail: fuzzyman AT voidspace DOT org DOT uk # -# mock 1.0.1 +# mock 2.0.0 # http://www.voidspace.org.uk/python/mock/ # # Copyright (c) 2007-2013, Michael Foord & the mock team From c496d2aa0147ff6401c1cbcc4dbf43ee7c6b2006 Mon Sep 17 00:00:00 2001 From: Scott Miller Date: Fri, 17 Mar 2017 13:45:59 -0400 Subject: [PATCH 004/388] push license properly to PyPi metadata --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 21e538e5..af841b80 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,6 +5,7 @@ home-page = https://github.com/testing-cabal/mock description-file = README.rst author = Testing Cabal author-email = testing-in-python@lists.idyll.org +license = OSI Approved :: BSD License classifier = Development Status :: 5 - Production/Stable Environment :: Console From ecdae68e58d985b2c805eb86c593f67ab7bc863e Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Oct 2017 17:07:29 +0300 Subject: [PATCH 005/388] Github -> GitHub --- README.rst | 2 +- docs/index.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index a7b632ce..1ff0ac4b 100644 --- a/README.rst +++ b/README.rst @@ -17,7 +17,7 @@ Please see the standard library documentation for more details. :License: `BSD License`_ :Support: `Mailing list (testing-in-python@lists.idyll.org) `_ -:Issue tracker: `Github Issues +:Issue tracker: `GitHub Issues `_ :Build status: .. image:: https://travis-ci.org/testing-cabal/mock.svg?branch=master diff --git a/docs/index.txt b/docs/index.txt index 3889ab82..0f3daf44 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -10,7 +10,7 @@ :License: `BSD License`_ :Support: `Mailing list (testing-in-python@lists.idyll.org) `_ -:Issue tracker: `Github Issues +:Issue tracker: `GitHub Issues `_ :Last sync: cb6aab1248c4aec4dd578bea717854505a6fb55d @@ -64,7 +64,7 @@ The current version is |release|. Mock is stable and widely used. .. index:: repository .. index:: git -You can checkout the latest development version from Github +You can checkout the latest development version from GitHub repository with the following command: ``git clone https://github.com/testing-cabal/mock`` From b67fe8a6f55ba321b602740fdf0bf0d15f074f4f Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 28 Oct 2017 16:01:45 +0300 Subject: [PATCH 006/388] Drop support for EOL Python <= 2.6 & 3.0-3.2, add 3.5 & 3.6 --- .travis.yml | 3 ++- README.rst | 4 ++-- docs/index.txt | 17 +++++++++++------ mock/mock.py | 6 ------ mock/tests/support.py | 22 ---------------------- mock/tests/testmock.py | 6 ++---- mock/tests/testpatch.py | 4 ++-- setup.cfg | 8 ++------ tools/pre-applypatch | 1 - tox.ini | 20 +------------------- 10 files changed, 22 insertions(+), 69 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7f855dd0..a13b528d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,11 @@ sudo: false language: python python: - - "2.6" - "2.7" - "3.3" - "3.4" + - "3.5" + - "3.6" - pypy - pypy3 matrix: diff --git a/README.rst b/README.rst index a7b632ce..2ba1b382 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.6 and up, and 3.3 and up. +compatible with Python 2.7 and 3.3 and up. Please see the standard library documentation for more details. @@ -17,7 +17,7 @@ Please see the standard library documentation for more details. :License: `BSD License`_ :Support: `Mailing list (testing-in-python@lists.idyll.org) `_ -:Issue tracker: `Github Issues +:Issue tracker: `GitHub Issues `_ :Build status: .. image:: https://travis-ci.org/testing-cabal/mock.svg?branch=master diff --git a/docs/index.txt b/docs/index.txt index 3889ab82..6c8e37b0 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -10,7 +10,7 @@ :License: `BSD License`_ :Support: `Mailing list (testing-in-python@lists.idyll.org) `_ -:Issue tracker: `Github Issues +:Issue tracker: `GitHub Issues `_ :Last sync: cb6aab1248c4aec4dd578bea717854505a6fb55d @@ -45,9 +45,12 @@ the newest features from the latest release of Python available for all Pythons. The ``mock`` package contains a rolling backport of the standard library mock -code compatible with Python 2.6 and up, and 3.3 and up. Python 3.2 is supported -by mock 1.3.0 and below - with pip no longer supporting 3.2, we cannot test -against that version anymore. +code compatible with Python 2.7 and 3.3 and up. + +* Python 2.6 is supported by mock 2.0.0 and below. + +* Python 3.2 is supported by mock 1.3.0 and below - with pip no longer +supporting 3.2, we cannot test against that version anymore. Please see the standard library documentation for usage details. @@ -64,7 +67,7 @@ The current version is |release|. Mock is stable and widely used. .. index:: repository .. index:: git -You can checkout the latest development version from Github +You can checkout the latest development version from GitHub repository with the following command: ``git clone https://github.com/testing-cabal/mock`` @@ -110,6 +113,8 @@ Older Versions of Python Version 1.0.1 is the last version compatible with Python < 2.6. +Version 2.0.0 is the last version compatible with Python 2.6. + .. index:: maintainer notes Maintainer Notes @@ -123,7 +128,7 @@ 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.6, 2.7, 3.3, 3.4, +mock is CI tested using Travis-CI on Python versions 2.7, 3.3, 3.4, 3.5, nightly Python 3 builds, pypy, pypy3. Jython support is desired, if someone could contribute a patch to .travis.yml to support it that would be excellent. diff --git a/mock/mock.py b/mock/mock.py index f246f2a3..7e4bb768 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -94,12 +94,6 @@ # Python 3 long = int -try: - BaseException -except NameError: - # Python 2.4 compatibility - BaseException = Exception - if six.PY2: # Python 2's next() can't handle a non-iterator with a __next__ method. _next = next diff --git a/mock/tests/support.py b/mock/tests/support.py index 8e2082ff..c7ad20b8 100644 --- a/mock/tests/support.py +++ b/mock/tests/support.py @@ -1,19 +1,3 @@ -import sys - -info = sys.version_info -import unittest2 - - -try: - callable = callable -except NameError: - def callable(obj): - return hasattr(obj, '__call__') - - -with_available = sys.version_info[:2] >= (2, 5) - - def is_instance(obj, klass): """Version of is_instance that doesn't access __class__""" return issubclass(type(obj), klass) @@ -28,9 +12,3 @@ def wibble(self): class X(object): pass - -try: - next = next -except NameError: - def next(obj): - return obj.next() diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 7511c23d..66323e9f 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -18,9 +18,7 @@ create_autospec ) from mock.mock import _CallList -from mock.tests.support import ( - callable, is_instance, next -) +from mock.tests.support import is_instance try: @@ -738,7 +736,7 @@ def __call__(self, a): def test_dir(self): mock = Mock() attrs = set(dir(mock)) - type_attrs = set([m for m in dir(Mock) if not m.startswith('_')]) + type_attrs = {m for m in dir(Mock) if not m.startswith('_')} # all public attributes from the type are included self.assertEqual(set(), type_attrs - attrs) diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index 32a6c271..f31ccef0 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -9,7 +9,7 @@ import unittest2 as unittest from mock.tests import support -from mock.tests.support import SomeClass, is_instance, callable +from mock.tests.support import SomeClass, is_instance from mock import ( NonCallableMock, CallableMixin, patch, sentinel, @@ -1340,7 +1340,7 @@ def test_patch_multiple_create_mocks_patcher(self): try: f = result['f'] foo = result['foo'] - self.assertEqual(set(result), set(['f', 'foo'])) + self.assertEqual(set(result), {'f', 'foo'}) self.assertIs(Foo, original_foo) self.assertIs(Foo.f, f) diff --git a/setup.cfg b/setup.cfg index 21e538e5..25c280b9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,13 +13,12 @@ classifier = Operating System :: OS Independent Programming Language :: Python Programming Language :: Python :: 2 - Programming Language :: Python :: 2.6 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 - Programming Language :: Python :: 3.2 Programming Language :: Python :: 3.3 Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: Jython Programming Language :: Python :: Implementation :: PyPy @@ -33,10 +32,7 @@ keyword = test = unittest2>=1.1.0 docs = - jinja2<2.7:python_version<"3.3" and python_version>="3" - Pygments<2:python_version<"3.3" and python_version>="3" - sphinx<1.3:python_version<"3.3" and python_version>="3" - sphinx:python_version<"3" or python_version>="3.3" + sphinx [files] packages = mock diff --git a/tools/pre-applypatch b/tools/pre-applypatch index 31985340..28ab636a 100755 --- a/tools/pre-applypatch +++ b/tools/pre-applypatch @@ -26,7 +26,6 @@ function test_version { find . -name "*.pyc" -exec rm "{}" \; -test_version 2.6 test_version 2.7 test_version 3.3 test_version 3.4 diff --git a/tox.ini b/tox.ini index 58e29d2b..d7fef31f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,19 +1,10 @@ [tox] -envlist = py25,py26,py27,py31,pypy,py32,py33,jython +envlist = py27,pypy,py33,jython [testenv] deps=unittest2 commands={envbindir}/unit2 discover [] -[testenv:py26] -commands= - {envbindir}/unit2 discover [] - {envbindir}/sphinx-build -E -b doctest docs html - {envbindir}/sphinx-build -E docs html -deps = - unittest2 - sphinx - [testenv:py27] commands= {envbindir}/unit2 discover [] @@ -22,15 +13,6 @@ deps = unittest2 sphinx -[testenv:py31] -deps = - unittest2py3k - -[testenv:py32] -commands= - {envbindir}/python -m unittest discover [] -deps = - [testenv:py33] commands= {envbindir}/python -m unittest discover [] From 07532a75003ec8e871e494f9f1ab8f28fa246f08 Mon Sep 17 00:00:00 2001 From: Harrison Gregg Date: Fri, 19 Jan 2018 13:52:12 -0500 Subject: [PATCH 007/388] Fix assert_xxx methods on autospec functions Add functions assert_called, assert_not_called, and assert_called_once to functions when using autospec. --- mock/mock.py | 9 +++++++++ mock/tests/testpatch.py | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/mock/mock.py b/mock/mock.py index 7e4bb768..def8c0e5 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -306,6 +306,12 @@ def _setup_func(funcopy, mock): if not _is_instance_mock(mock): return + 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): @@ -338,6 +344,9 @@ def reset_mock(): funcopy.assert_has_calls = assert_has_calls funcopy.assert_any_call = assert_any_call funcopy.reset_mock = reset_mock + funcopy.assert_called = assert_called + funcopy.assert_not_called = assert_not_called + funcopy.assert_called_once = assert_called_once mock._mock_delegate = funcopy diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index f31ccef0..7c480a1e 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -974,8 +974,14 @@ def function(mock): def test_autospec_function(self): @patch('%s.function' % __name__, autospec=True) def test(mock): + function.assert_not_called() + self.assertRaises(AssertionError, function.assert_called) + self.assertRaises(AssertionError, function.assert_called_once) function(1) + self.assertRaises(AssertionError, function.assert_not_called) function.assert_called_with(1) + function.assert_called() + function.assert_called_once() function(2, 3) function.assert_called_with(2, 3) From 34e5101e1411d19f51f5f9c5c88401e6262c2641 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Thu, 26 Apr 2018 06:00:29 -0700 Subject: [PATCH 008/388] Update all pypi.python.org URLs to pypi.org For details on the new PyPI, see the blog post: https://pythoninsider.blogspot.ca/2018/04/new-pypi-launched-legacy-pypi-shutting.html --- README.rst | 2 +- docs/index.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 2ba1b382..882e067f 100644 --- a/README.rst +++ b/README.rst @@ -26,4 +26,4 @@ Please see the standard library documentation for more details. .. _Mock Homepage: https://github.com/testing-cabal/mock .. _BSD License: http://github.com/testing-cabal/mock/blob/master/LICENSE.txt .. _Python Docs: https://docs.python.org/dev/library/unittest.mock.html -.. _mock on PyPI: http://pypi.python.org/pypi/mock +.. _mock on PyPI: https://pypi.org/project/mock/ diff --git a/docs/index.txt b/docs/index.txt index 6c8e37b0..082f7573 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -62,7 +62,7 @@ Installing The current version is |release|. Mock is stable and widely used. -* `mock on PyPI `_ +* `mock on PyPI `_ .. index:: repository .. index:: git @@ -90,7 +90,7 @@ unpacking run: Bug Reports +++++++++++ -Mock uses `unittest2 `_ for its own +Mock uses `unittest2 `_ for its own Issues with the backport process, such as compatibility with a particular Python, should be reported to the `bug tracker `_. Feature requests and issues From 59130a4f8393cdfca381933ffb73da5d0991c8c9 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 8 May 2018 08:23:53 +0300 Subject: [PATCH 009/388] Drop support for EOL Python 3.3 --- .travis.yml | 1 - README.rst | 4 ++-- docs/index.txt | 8 ++++---- setup.cfg | 1 - tools/pre-applypatch | 1 - tox.ini | 2 +- 6 files changed, 7 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index a13b528d..05d80dba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ sudo: false language: python python: - "2.7" - - "3.3" - "3.4" - "3.5" - "3.6" diff --git a/README.rst b/README.rst index 2ba1b382..b152c2f7 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.3 and up. +compatible with Python 2.7 and 3.4 and up. Please see the standard library documentation for more details. @@ -24,6 +24,6 @@ Please see the standard library documentation for more details. :target: https://travis-ci.org/testing-cabal/mock .. _Mock Homepage: https://github.com/testing-cabal/mock -.. _BSD License: http://github.com/testing-cabal/mock/blob/master/LICENSE.txt +.. _BSD License: https://github.com/testing-cabal/mock/blob/master/LICENSE.txt .. _Python Docs: https://docs.python.org/dev/library/unittest.mock.html .. _mock on PyPI: http://pypi.python.org/pypi/mock diff --git a/docs/index.txt b/docs/index.txt index 6c8e37b0..39770f10 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -45,9 +45,9 @@ the newest features from the latest release of Python available for all Pythons. The ``mock`` package contains a rolling backport of the standard library mock -code compatible with Python 2.7 and 3.3 and up. +code compatible with Python 2.7 and 3.4 and up. -* Python 2.6 is supported by mock 2.0.0 and below. +* Python 2.6 and 3.3 are supported by mock 2.0.0 and below. * Python 3.2 is supported by mock 1.3.0 and below - with pip no longer supporting 3.2, we cannot test against that version anymore. @@ -128,8 +128,8 @@ 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.3, 3.4, -3.5, nightly Python 3 builds, pypy, pypy3. Jython support is desired, if +mock is CI tested using Travis-CI on Python versions 2.7, 3.4, +3.5, 3.6, nightly Python 3 builds, pypy, pypy3. Jython support is desired, if someone could contribute a patch to .travis.yml to support it that would be excellent. diff --git a/setup.cfg b/setup.cfg index 25c280b9..f2ac9544 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,7 +15,6 @@ classifier = Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 - Programming Language :: Python :: 3.3 Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 diff --git a/tools/pre-applypatch b/tools/pre-applypatch index 28ab636a..2e912f28 100755 --- a/tools/pre-applypatch +++ b/tools/pre-applypatch @@ -27,7 +27,6 @@ function test_version { find . -name "*.pyc" -exec rm "{}" \; test_version 2.7 -test_version 3.3 test_version 3.4 test_version 3.5 test_version cpython diff --git a/tox.ini b/tox.ini index d7fef31f..76c3bd3a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27,pypy,py33,jython +envlist = py27,pypy,jython [testenv] deps=unittest2 From 325420fb6406efc78ccef71ca29e1889b21bbada Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Oct 2017 17:02:14 +0300 Subject: [PATCH 010/388] Add Python 3.6 --- setup.cfg | 2 +- tools/pre-applypatch | 1 + tox.ini | 19 +++++++++++++++++-- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index 25c280b9..8238a8a8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,7 +5,7 @@ home-page = https://github.com/testing-cabal/mock description-file = README.rst author = Testing Cabal author-email = testing-in-python@lists.idyll.org -classifier = +classifier = Development Status :: 5 - Production/Stable Environment :: Console Intended Audience :: Developers diff --git a/tools/pre-applypatch b/tools/pre-applypatch index 28ab636a..b716385b 100755 --- a/tools/pre-applypatch +++ b/tools/pre-applypatch @@ -30,6 +30,7 @@ test_version 2.7 test_version 3.3 test_version 3.4 test_version 3.5 +test_version 3.6 test_version cpython test_version pypy diff --git a/tox.ini b/tox.ini index d7fef31f..3ca858aa 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27,pypy,py33,jython +envlist = py27,pypy,py33,py34,py35,py36,jython [testenv] deps=unittest2 @@ -18,5 +18,20 @@ commands= {envbindir}/python -m unittest discover [] deps = +[testenv:py34] +commands= + {envbindir}/python -m unittest discover [] +deps = + +[testenv:py35] +commands= + {envbindir}/python -m unittest discover [] +deps = + +[testenv:py36] +commands= + {envbindir}/python -m unittest discover [] +deps = + # note for jython. Execute in tests directory: -# rm `find . -name '*$py.class'` \ No newline at end of file +# rm `find . -name '*$py.class'` From ef76c6a1dce5c6a552b01123a01a4b8aa4501f82 Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 25 Jul 2018 14:15:53 +0300 Subject: [PATCH 011/388] Upgrade Python syntax with pyupgrade --- mock/mock.py | 32 ++++++++++++++++---------------- mock/tests/testmock.py | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/mock/mock.py b/mock/mock.py index 7e4bb768..23ac55c7 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -105,7 +105,7 @@ def next(obj, _next=_next): del _next -_builtins = set(name for name in dir(builtins) if not name.startswith('_')) +_builtins = {name for name in dir(builtins) if not name.startswith('_')} BaseExceptions = (BaseException,) if 'java' in sys.platform: @@ -389,11 +389,11 @@ def _copy(value): if six.PY2: ClassTypes = (type, ClassType) -_allowed_names = set(( +_allowed_names = { 'return_value', '_mock_return_value', 'side_effect', '_mock_side_effect', '_mock_parent', '_mock_new_parent', '_mock_name', '_mock_new_name' -)) +} def _delegating_property(name): @@ -764,7 +764,7 @@ def __repr__(self): if self._spec_set: spec_string = ' spec_set=%r' spec_string = spec_string % self._spec_class.__name__ - return "<%s%s%s id='%s'>" % ( + return "<{}{}{} id='{}'>".format( type(self).__name__, name_string, spec_string, @@ -916,13 +916,13 @@ def assert_called_with(_mock_self, *args, **kwargs): self = _mock_self if self.call_args is None: expected = self._format_mock_call_signature(args, kwargs) - raise AssertionError('Expected call: %s\nNot called' % (expected,)) + raise AssertionError('Expected call: {}\nNot called'.format(expected)) def _error_message(cause): 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 = '%s\n%s' % (msg, str(cause)) + msg = '{}\n{}'.format(msg, str(cause)) return msg expected = self._call_matcher((args, kwargs)) actual = self._call_matcher(self.call_args) @@ -973,7 +973,7 @@ def assert_has_calls(self, calls, any_order=False): not_found.append(kall) if not_found: six.raise_from(AssertionError( - '%r not all found in call list' % (tuple(not_found),) + '{!r} not all found in call list'.format(tuple(not_found)) ), cause) @@ -1334,7 +1334,7 @@ def get_original(self): if not self.create and original is DEFAULT: raise AttributeError( - "%s does not have the attribute %r" % (target, name) + "{} does not have the attribute {!r}".format(target, name) ) return original, local @@ -1837,13 +1837,13 @@ def _patch_stopall(): # (as they are metaclass methods) # __del__ is not supported at all as it causes problems if it exists -_non_defaults = set(( +_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__', -)) +} def _get_method(name, func): @@ -1854,19 +1854,19 @@ def method(self, *args, **kw): return method -_magics = set( +_magics = { '__%s__' % method for method in ' '.join([magic_methods, numerics, inplace, right, extra]).split() -) +} _all_magics = _magics | _non_defaults -_unsupported_magics = set(( +_unsupported_magics = { '__getattr__', '__setattr__', '__init__', '__new__', '__prepare__' '__instancecheck__', '__subclasscheck__', '__del__' -)) +} _calculate_return_value = { '__hash__': lambda self: object.__hash__(self), @@ -2069,7 +2069,7 @@ def encode_item(item): return item kwargs_string = ', '.join([ - '%s=%r' % (encode_item(key), value) for key, value in sorted(kwargs.items()) + '{}={!r}'.format(encode_item(key), value) for key, value in sorted(kwargs.items()) ]) if args_string: formatted_args = args_string @@ -2208,7 +2208,7 @@ def __call__(self, *args, **kwargs): def __getattr__(self, attr): if self.name is None: return _Call(name=attr, from_kall=False) - name = '%s.%s' % (self.name, attr) + name = '{}.{}'.format(self.name, attr) return _Call(name=name, parent=self, from_kall=False) diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 66323e9f..6e52b278 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -811,7 +811,7 @@ def assertRaisesWithMsg(self, exception, message, func, *args, **kwargs): instance = sys.exc_info()[1] self.assertIsInstance(instance, exception) else: - self.fail('Exception %r not raised' % (exception,)) + self.fail('Exception {!r} not raised'.format(exception)) msg = str(instance) self.assertEqual(msg, message) From 9ad0dd8a1fac2e03be1fe3b44eb3b198994586ad Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 25 Jul 2018 14:18:11 +0300 Subject: [PATCH 012/388] Add python_requires to help pip --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 5f727ae7..6154ba71 100755 --- a/setup.py +++ b/setup.py @@ -3,4 +3,5 @@ setuptools.setup( setup_requires=['pbr>=1.3', 'setuptools>=17.1'], + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', pbr=True) From 68de8c8c2efa246c464dc4618457115e9cd11e5d Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sun, 16 Sep 2018 17:23:01 -0700 Subject: [PATCH 013/388] Clean up unused imports --- mock/tests/testmagicmethods.py | 1 - mock/tests/testpatch.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/mock/tests/testmagicmethods.py b/mock/tests/testmagicmethods.py index f47a2025..101d9e54 100644 --- a/mock/tests/testmagicmethods.py +++ b/mock/tests/testmagicmethods.py @@ -11,7 +11,6 @@ unicode = str long = int -import inspect import sys import textwrap diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index f31ccef0..8b1f9c6a 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -13,7 +13,7 @@ from mock import ( NonCallableMock, CallableMixin, patch, sentinel, - MagicMock, Mock, NonCallableMagicMock, patch, + MagicMock, Mock, NonCallableMagicMock, DEFAULT, call ) from mock.mock import _patch, _get_target From 89123d51895bdb828a7773228acc08b67d73255c Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sun, 16 Sep 2018 17:20:15 -0700 Subject: [PATCH 014/388] Drop dependency of unittest2; use stdlib unittest instead Now that Python 2.6 support has been dropped (commit b67fe8a6f55ba321b602740fdf0bf0d15f074f4f), can simply use Python unittest. No unique features of unittest2 were in use that aren't also available in the stdlib unittest. One less dependency. --- .travis.yml | 2 +- docs/conf.py | 1 - docs/index.txt | 2 +- mock/tests/testcallable.py | 2 +- mock/tests/testhelpers.py | 2 +- mock/tests/testmagicmethods.py | 4 +- mock/tests/testmock.py | 14 ++--- mock/tests/testpatch.py | 2 +- mock/tests/testsentinel.py | 2 +- mock/tests/testwith.py | 2 +- setup.cfg | 2 - tox.ini | 13 ++--- unittest.cfg | 95 ---------------------------------- 13 files changed, 19 insertions(+), 124 deletions(-) delete mode 100644 unittest.cfg diff --git a/.travis.yml b/.travis.yml index a13b528d..b80f4443 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,6 @@ install: - pip list - python --version script: - - unit2 + - python -m unittest discover - if [ -z "$SKIP_DOCS" ]; then python setup.py build_sphinx; fi - rst2html.py --strict README.rst README.html diff --git a/docs/conf.py b/docs/conf.py index d32357da..6368a01e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -33,7 +33,6 @@ import sys import mock from mock import * # yeah, I know :-/ -import unittest2 import __main__ if os.getcwd() not in sys.path: diff --git a/docs/index.txt b/docs/index.txt index 6c8e37b0..9f6e3c29 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -90,7 +90,7 @@ unpacking run: Bug Reports +++++++++++ -Mock uses `unittest2 `_ for its own +Mock uses `unittest `_ for its own Issues with the backport process, such as compatibility with a particular Python, should be reported to the `bug tracker `_. Feature requests and issues diff --git a/mock/tests/testcallable.py b/mock/tests/testcallable.py index 10acdc35..c3cf2e5f 100644 --- a/mock/tests/testcallable.py +++ b/mock/tests/testcallable.py @@ -2,7 +2,7 @@ # E-mail: fuzzyman AT voidspace DOT org DOT uk # http://www.voidspace.org.uk/python/mock/ -import unittest2 as unittest +import unittest from mock.tests.support import is_instance, X, SomeClass from mock import ( diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index a87df1b3..eb38b21c 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -3,7 +3,7 @@ # http://www.voidspace.org.uk/python/mock/ import six -import unittest2 as unittest +import unittest from mock import ( call, create_autospec, MagicMock, diff --git a/mock/tests/testmagicmethods.py b/mock/tests/testmagicmethods.py index f47a2025..73e5a0fe 100644 --- a/mock/tests/testmagicmethods.py +++ b/mock/tests/testmagicmethods.py @@ -14,9 +14,9 @@ import inspect import sys import textwrap +import unittest import six -import unittest2 as unittest from mock import Mock, MagicMock from mock.mock import _magics @@ -405,7 +405,7 @@ def test_setting_unsupported_magic_method(self): mock = MagicMock() def set_setattr(): mock.__setattr__ = lambda self, name: None - self.assertRaisesRegex(AttributeError, + self.assertRaisesRegexp(AttributeError, "Attempting to set unsupported magic method '__setattr__'.", set_setattr ) diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 66323e9f..f7049c6c 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -8,7 +8,7 @@ import tempfile import six -import unittest2 as unittest +import unittest import mock from mock import ( @@ -205,7 +205,7 @@ def f(): mock = create_autospec(f) mock.side_effect = ValueError('Bazinga!') - self.assertRaisesRegex(ValueError, 'Bazinga!', mock) + self.assertRaisesRegexp(ValueError, 'Bazinga!', mock) @unittest.skipUnless('java' in sys.platform, 'This test only applies to Jython') @@ -501,7 +501,7 @@ def test_only_allowed_methods_exist(self): # this should be allowed mock.something - self.assertRaisesRegex( + self.assertRaisesRegexp( AttributeError, "Mock object has no attribute 'something_else'", getattr, mock, 'something_else' @@ -520,12 +520,12 @@ def test_attributes(mock): mock.x mock.y mock.__something__ - self.assertRaisesRegex( + self.assertRaisesRegexp( AttributeError, "Mock object has no attribute 'z'", getattr, mock, 'z' ) - self.assertRaisesRegex( + self.assertRaisesRegexp( AttributeError, "Mock object has no attribute '__foobar__'", getattr, mock, '__foobar__' @@ -591,13 +591,13 @@ def test_baseexceptional_side_effect(self): def test_assert_called_with_message(self): mock = Mock() - self.assertRaisesRegex(AssertionError, 'Not called', + self.assertRaisesRegexp(AssertionError, 'Not called', mock.assert_called_with) def test_assert_called_once_with_message(self): mock = Mock(name='geoffrey') - self.assertRaisesRegex(AssertionError, + self.assertRaisesRegexp(AssertionError, r"Expected 'geoffrey' to be called once\.", mock.assert_called_once_with) diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index f31ccef0..16fa1683 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -6,7 +6,7 @@ import sys import six -import unittest2 as unittest +import unittest from mock.tests import support from mock.tests.support import SomeClass, is_instance diff --git a/mock/tests/testsentinel.py b/mock/tests/testsentinel.py index 69b20427..3253fa3c 100644 --- a/mock/tests/testsentinel.py +++ b/mock/tests/testsentinel.py @@ -2,7 +2,7 @@ # E-mail: fuzzyman AT voidspace DOT org DOT uk # http://www.voidspace.org.uk/python/mock/ -import unittest2 as unittest +import unittest from mock import sentinel, DEFAULT diff --git a/mock/tests/testwith.py b/mock/tests/testwith.py index aa7812b3..ad340ab5 100644 --- a/mock/tests/testwith.py +++ b/mock/tests/testwith.py @@ -4,7 +4,7 @@ from warnings import catch_warnings -import unittest2 as unittest +import unittest from mock.tests.support import is_instance from mock import MagicMock, Mock, patch, sentinel, mock_open, call diff --git a/setup.cfg b/setup.cfg index 25c280b9..02a9b7a9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -29,8 +29,6 @@ keyword = testing, test, mock, mocking, unittest, patching, stubs, fakes, doubles [extras] -test = - unittest2>=1.1.0 docs = sphinx diff --git a/tox.ini b/tox.ini index d7fef31f..4ac890bd 100644 --- a/tox.ini +++ b/tox.ini @@ -2,21 +2,14 @@ envlist = py27,pypy,py33,jython [testenv] -deps=unittest2 -commands={envbindir}/unit2 discover [] +commands={envbindir}/python -m unittest discover [] [testenv:py27] commands= - {envbindir}/unit2 discover [] + {envbindir}/python -m unittest discover [] {envbindir}/sphinx-build -E -b doctest docs html deps = - unittest2 sphinx -[testenv:py33] -commands= - {envbindir}/python -m unittest discover [] -deps = - # note for jython. Execute in tests directory: -# rm `find . -name '*$py.class'` \ No newline at end of file +# rm `find . -name '*$py.class'` diff --git a/unittest.cfg b/unittest.cfg deleted file mode 100644 index b2d6f674..00000000 --- a/unittest.cfg +++ /dev/null @@ -1,95 +0,0 @@ - -[unittest] -plugins = - unittest2.plugins.debugger - unittest2.plugins.checker - unittest2.plugins.doctestloader - unittest2.plugins.matchregexp - unittest2.plugins.moduleloading - unittest2.plugins.testcoverage - unittest2.plugins.growl - unittest2.plugins.filtertests - unittest2.plugins.junitxml - unittest2.plugins.timed - unittest2.plugins.counttests - unittest2.plugins.logchannels - -excluded-plugins = - -# 0, 1 or 2 (default is 1) -# quiet, normal or verbose -# can be overriden at command line -verbosity = normal - -# true or false -# even if false can be switched on at command line -catch = -buffer = -failfast = - - -[matchregexp] -always-on = False -full-path = True - -[debugger] -always-on = False -errors-only = True - -[coverage] -always-on = False -config = -report-html = False -# only used if report-html is false -annotate = False -# defaults to './htmlcov/' -html-directory = -# if unset will output to console -text-file = -branch = False -timid = False -cover-pylib = False -exclude-lines = - # Have to re-enable the standard pragma - pragma: no cover - - # Don't complain about missing debug-only code: - def __repr__ - if self\.debug - - # Don't complain if tests don't hit defensive assertion code: - raise AssertionError - raise NotImplementedError - - # Don't complain if non-runnable code isn't run: - if 0: - if __name__ == .__main__. - -ignore-errors = False -modules = - -[growl] -always-on = False - -[doctest] -always-on = False - -[module-loading] -always-on = False - -[checker] -always-on = False -pep8 = False -pyflakes = True - -[junit-xml] -always-on = False -path = junit.xml - -[timed] -always-on = True -threshold = 0.01 - -[count] -always-on = True -enhanced = False From 0f7f20fe3c52660c42dcaf304e6ece92a785974e Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sun, 16 Sep 2018 18:04:59 -0700 Subject: [PATCH 015/388] Fix Sphinx warnings Warnings previously appeared as: mock/docs/index.txt:53: WARNING: Bullet list ends without a blank line; unexpected unindent. mock/docs/index.txt:123: WARNING: Title level inconsistent: --- docs/index.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/index.txt b/docs/index.txt index 6c8e37b0..00ffccc0 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -1,6 +1,6 @@ -==================================== +=================================== Mock - Mocking and Testing Library -==================================== +=================================== :Version: |release| :Date: |today| @@ -50,7 +50,7 @@ code compatible with Python 2.7 and 3.3 and up. * Python 2.6 is supported by mock 2.0.0 and below. * Python 3.2 is supported by mock 1.3.0 and below - with pip no longer -supporting 3.2, we cannot test against that version anymore. + supporting 3.2, we cannot test against that version anymore. Please see the standard library documentation for usage details. @@ -121,7 +121,7 @@ Maintainer Notes ++++++++++++++++ Development -=========== +----------- Checkout from git (see :ref:`installing`) and submit pull requests. @@ -134,7 +134,7 @@ someone could contribute a patch to .travis.yml to support it that would be excellent. Releasing -========= +--------- NB: please use semver. Bump the major component on API breaks, minor on all non-bugfix changes, patch on bugfix only changes. @@ -144,13 +144,13 @@ 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)``. Backporting process -=================== +------------------- 1. Patch your git am with `my patch `_. 2. Install the applypatch-transform hook from tools/ to your .git hooks dir. From 0f4691370669358c35a118bd05fbb1fdc79892d1 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 29 Nov 2018 08:46:15 +0000 Subject: [PATCH 016/388] fix another license url --- docs/index.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.txt b/docs/index.txt index 66a1a091..75de1b78 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -15,7 +15,7 @@ :Last sync: cb6aab1248c4aec4dd578bea717854505a6fb55d .. _Mock Homepage: https://github.com/testing-cabal/mock -.. _BSD License: http://github.com/testing-cabal/mock/blob/master/LICENSE.txt +.. _BSD License: https://github.com/testing-cabal/mock/blob/master/LICENSE.txt .. _Python Docs: https://docs.python.org/dev/library/unittest.mock.html .. module:: mock From 667054a83a30102fccb65188e4c20f37789cf95c Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 29 Nov 2018 08:50:31 +0000 Subject: [PATCH 017/388] Don't fail the build when a test on pypy breaks. Tracking on https://github.com/testing-cabal/mock/issues/438. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 38183008..0f67b8f0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,8 @@ matrix: # doesn't happen in trunk. - python: "nightly" env: SKIP_DOCS=1 + allow_failures: + - python: pypy install: - pip install -U pip - pip install -U wheel setuptools From afb358270bb9e781549c83e68682ccb7f948648d Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 29 Nov 2018 08:52:00 +0000 Subject: [PATCH 018/388] add Python 3.7 into the mix --- .travis.yml | 8 ++++---- setup.cfg | 1 + tox.ini | 8 +++++++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0f67b8f0..241974ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,3 @@ -sudo: false language: python python: - "2.7" @@ -7,12 +6,13 @@ python: - "3.6" - pypy - pypy3 + - nightly matrix: include: -# Travis nightly look to be 3.5.0a4, b3 is out and the syntax error we see -# doesn't happen in trunk. + - python: "3.7" + dist: xenial - python: "nightly" - env: SKIP_DOCS=1 + dist: xenial allow_failures: - python: pypy install: diff --git a/setup.cfg b/setup.cfg index 88b3bd36..f99b6041 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,6 +19,7 @@ classifier = Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: Jython Programming Language :: Python :: Implementation :: PyPy diff --git a/tox.ini b/tox.ini index d11dc525..99a8322d 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27,pypy,py34,py35,py36,jython +envlist = py27,pypy,py34,py35,py36,py37jython [testenv] commands={envbindir}/python -m unittest discover [] @@ -25,5 +25,11 @@ deps = commands= {envbindir}/python -m unittest discover [] deps = + +[testenv:py37] +commands= + {envbindir}/python -m unittest discover [] +deps = + # note for jython. Execute in tests directory: # rm `find . -name '*$py.class'` From b80f791bdb1e4a68b8981ff8afe8643748328545 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Thu, 29 Nov 2018 07:21:06 -0800 Subject: [PATCH 019/388] Simplify tox configuration - All testenv commands were the same so only define once. - Move docs to its own testenv and use the same command as Travis. - Replace deprecated [] with {posargs} --- tox.ini | 35 ++++++----------------------------- 1 file changed, 6 insertions(+), 29 deletions(-) diff --git a/tox.ini b/tox.ini index 99a8322d..be80fed6 100644 --- a/tox.ini +++ b/tox.ini @@ -1,35 +1,12 @@ [tox] -envlist = py27,pypy,py34,py35,py36,py37jython +envlist = py27,pypy,py34,py35,py36,py37jython,docs [testenv] -commands={envbindir}/python -m unittest discover [] +commands = + {envbindir}/python -m unittest discover {posargs} -[testenv:py27] -commands= - {envbindir}/python -m unittest discover [] - {envbindir}/sphinx-build -E -b doctest docs html +[testenv:docs] deps = sphinx - -[testenv:py34] -commands= - {envbindir}/python -m unittest discover [] -deps = - -[testenv:py35] -commands= - {envbindir}/python -m unittest discover [] -deps = - -[testenv:py36] -commands= - {envbindir}/python -m unittest discover [] -deps = - -[testenv:py37] -commands= - {envbindir}/python -m unittest discover [] -deps = - -# note for jython. Execute in tests directory: -# rm `find . -name '*$py.class'` +commands = + {envbindir}/python setup.py build_sphinx From c979e7515474ae26de92e7615e09d9877dc978af Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 29 Nov 2018 21:32:06 +0000 Subject: [PATCH 020/388] remove duplicate nightly run --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 241974ae..4e5efed9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,6 @@ python: - "3.6" - pypy - pypy3 - - nightly matrix: include: - python: "3.7" From 6db8d961ac42ab6b4bd4e75e5925e6fa373135fc Mon Sep 17 00:00:00 2001 From: Aaron Gallagher Date: Tue, 17 Jan 2017 15:38:57 -0800 Subject: [PATCH 021/388] Fix autospec's behavior on method-bound builtins. Cython will, in the right circumstances, offer a MethodType instance where im_func is a builtin function. Any instance of MethodType is automatically assumed to be a python-defined function (more specifically, a function that has an inspectable signature), but _set_signature was still conservative in its assumptions. As a result _set_signature would return early with None instead of a mock since the im_func had no inspectable signature. This causes problems deeper inside mock, as _set_signature is assumed to _always_ return a mock, and nothing checked its return value. In similar corner cases, autospec will simply not check the spec of the function, so _set_signature is amended to now return early with the original, not-wrapped mock object. --- mock/mock.py | 2 +- mock/tests/testhelpers.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/mock/mock.py b/mock/mock.py index 8e05f49f..7f7625f3 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -280,7 +280,7 @@ def _set_signature(mock, original, instance=False): skipfirst = isinstance(original, ClassTypes) result = _get_signature_object(original, instance, skipfirst) if result is None: - return + return mock func, sig = result def checksig(*args, **kwargs): sig.bind(*args, **kwargs) diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index eb38b21c..2bfe2c8d 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -3,6 +3,7 @@ # http://www.voidspace.org.uk/python/mock/ import six +import time import unittest from mock import ( @@ -885,6 +886,18 @@ class Foo(object): mock_slot.assert_called_once_with(1, 2, 3) mock_slot.abc.assert_called_once_with(4, 5, 6) + def test_autospec_on_bound_builtin_function(self): + meth = six.create_bound_method(time.ctime, time.time()) + self.assertIsInstance(meth(), str) + mocked = create_autospec(meth) + + # no signature, so no spec to check against + mocked() + mocked.assert_called_once_with() + mocked.reset_mock() + mocked(4, 5, 6) + mocked.assert_called_once_with(4, 5, 6) + class TestCallList(unittest.TestCase): From bf8efd226c37cb064b8251b81b260f536cb0d630 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Fri, 30 Nov 2018 18:33:20 +0000 Subject: [PATCH 022/388] functools.partial has no func_defaults on Py2 also simplify _copy_func_details to match upstream. --- mock/mock.py | 32 ++++++++++++-------------------- mock/tests/testhelpers.py | 5 +++++ 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/mock/mock.py b/mock/mock.py index 7f7625f3..0aaae50b 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -206,29 +206,21 @@ def checksig(_mock_self, *args, **kwargs): def _copy_func_details(func, funcopy): - funcopy.__name__ = func.__name__ - funcopy.__doc__ = func.__doc__ - try: - funcopy.__text_signature__ = func.__text_signature__ - except AttributeError: - pass # we explicitly don't copy func.__dict__ into this copy as it would # expose original attributes that should be mocked - try: - funcopy.__module__ = func.__module__ - except AttributeError: - pass - try: - funcopy.__defaults__ = func.__defaults__ - except AttributeError: - pass - try: - funcopy.__kwdefaults__ = func.__kwdefaults__ - except AttributeError: - pass + for attribute in ( + '__name__', '__doc__', '__text_signature__', + '__module__', '__defaults__', '__kwdefaults__', + ): + try: + setattr(funcopy, attribute, getattr(func, attribute)) + except AttributeError: + pass if six.PY2: - funcopy.func_defaults = func.func_defaults - return + try: + funcopy.func_defaults = func.func_defaults + except AttributeError: + pass def _callable(obj): diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index 2bfe2c8d..ca31db38 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -1,6 +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 socket import six import time @@ -898,6 +899,10 @@ 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) + class TestCallList(unittest.TestCase): From 73bfd51b7185e9dc0c7016fb1a18f90ed083cbb5 Mon Sep 17 00:00:00 2001 From: xtreak Date: Sun, 23 Dec 2018 22:38:02 +0530 Subject: [PATCH 023/388] Skip failure test on pypy2 and make pypy2 mandatory --- .travis.yml | 2 -- mock/tests/testhelpers.py | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4e5efed9..ebcb0dbb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,8 +12,6 @@ matrix: dist: xenial - python: "nightly" dist: xenial - allow_failures: - - python: pypy install: - pip install -U pip - pip install -U wheel setuptools diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index ca31db38..2b2053f2 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -4,6 +4,7 @@ import socket import six +import sys import time import unittest @@ -459,6 +460,8 @@ class Sub(SomeClass): self._check_someclass_mock(mock) + @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 From bb815db2e55f8d872876d2e0ae233b1aab0048c9 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Fri, 26 Apr 2019 08:16:44 +0100 Subject: [PATCH 024/388] remove a copy of the version that's likely to get out of date --- mock/mock.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mock/mock.py b/mock/mock.py index 0aaae50b..b0d5365f 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2,7 +2,6 @@ # Test tools for mocking and patching. # E-mail: fuzzyman AT voidspace DOT org DOT uk # -# mock 2.0.0 # http://www.voidspace.org.uk/python/mock/ # # Copyright (c) 2007-2013, Michael Foord & the mock team From 51239e4b985ffb5895c8f7b1fed0a3174feea3a8 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Fri, 26 Apr 2019 08:31:26 +0100 Subject: [PATCH 025/388] Move last sync point to its own file. Since CPython has moved to github, I had to guestimate the point based on the last backport @rbtcollins did as the git mirror he was using evidently had different commit hashes: ``` $ git log --grep "Issue #26323" commit 2c2a4e63d794eb55e9163322ea11b9765e9e0db5 Author: Victor Stinner Date: Fri Mar 11 22:17:48 2016 +0100 Add Mock.assert_called() Issue #26323: Add assert_called() and assert_called_once() methods to unittest.mock.Mock. ``` --- docs/index.txt | 6 ++---- lastsync.txt | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) create mode 100644 lastsync.txt diff --git a/docs/index.txt b/docs/index.txt index 75de1b78..e870d362 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -12,7 +12,6 @@ `_ :Issue tracker: `GitHub Issues `_ -:Last sync: cb6aab1248c4aec4dd578bea717854505a6fb55d .. _Mock Homepage: https://github.com/testing-cabal/mock .. _BSD License: https://github.com/testing-cabal/mock/blob/master/LICENSE.txt @@ -158,10 +157,9 @@ Backporting process we support on each patch that is applied. I use containers, and a sample script is in tools/pre-applypatch. 4. Pull down the cPython git mirror: https://github.com/python/cpython.git -5. Export the new revisions since the ``Last sync`` at the top of this - document:: +5. Export the new revisions since the ``Last sync`:: - revs=${lastsync} + revs=`cat lastsync.txt` rm migrate-export git log --pretty="format:%H " $revs.. -- Lib/unittest/mock.py \ Lib/unittest/test/testmock/ > migrate-revs diff --git a/lastsync.txt b/lastsync.txt new file mode 100644 index 00000000..951fcfae --- /dev/null +++ b/lastsync.txt @@ -0,0 +1 @@ +2c2a4e63d794eb55e9163322ea11b9765e9e0db5 From 170d83e9338df2384b1dd0fcf9f11f0ac69be33b Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sat, 27 Apr 2019 13:41:55 +0100 Subject: [PATCH 026/388] Don't ignore rejected patches. Makes git --status more helpful! --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 93d11555..daa8ca64 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ .*\.pyc -*.rej html/ mock\.egg-info/ mock\.wpu From fb1f5b350d927bddcb4dd5878f551e9f10e7a9d4 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Fri, 26 Apr 2019 20:29:03 +0100 Subject: [PATCH 027/388] script for backporting patches from cpython. --- backport.py | 107 +++++++++++++++++++++++++++++++++++++++++++++++++ docs/index.txt | 4 +- 2 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 backport.py diff --git a/backport.py b/backport.py new file mode 100644 index 00000000..147e3e64 --- /dev/null +++ b/backport.py @@ -0,0 +1,107 @@ +import re +from argparse import ArgumentParser +from os.path import dirname, abspath +from subprocess import check_output, call + + +def git(command, repo): + return check_output('git '+command, cwd=repo, shell=True).decode() + + +def repo_state_bad(mock_repo): + status = git('status', mock_repo) + if 'You are in the middle of an am session' in status: + print(f'Mock repo at {mock_repo} needs cleanup:\n') + call('git status', shell=True) + return True + + +def cleanup_old_patches(mock_repo): + print('cleaning up old patches:') + call('rm -vf /tmp/*.mock.patch', shell=True) + call('find . -name "*.rej" -print -delete', shell=True, cwd=mock_repo) + + +def find_initial_cpython_rev(): + with open('lastsync.txt') as source: + return source.read().strip() + + +def cpython_revs_affecting_mock(cpython_repo, start): + revs = git(f'log --no-merges --format=%H {start}.. ' + f'-- Lib/unittest/mock.py Lib/unittest/test/testmock/', + repo=cpython_repo).split() + revs.reverse() + print(f'{len(revs)} patches to backport') + return revs + + +def has_been_backported(mock_repo, cpython_rev): + backport_rev = git(f'log --format=%H --grep "Backports: {cpython_rev}"', + repo=mock_repo).strip() + if backport_rev: + print(f'{cpython_rev} backported in {backport_rev}') + return True + print(f'{cpython_rev} has not been backported') + + +def extract_patch_for(cpython_repo, rev): + return git(f'format-patch -1 --no-stat --keep-subject --signoff --stdout {rev}', + repo=cpython_repo) + + +def munge(rev, patch): + + sign_off = 'Signed-off-by:' + patch = patch.replace(sign_off, f'Backports: {rev}\n{sign_off}', 1) + + for pattern, sub in ( + ('(a|b)/Lib/unittest/mock.py', r'\1/mock/mock.py'), + ('(a|b)/Lib/unittest/test/testmock/(.+)', r'\1/mock/tests/\2'), + ('(a|b)/Misc/NEWS', r'\1/NEWS'), + ): + patch = re.sub(pattern, sub, patch) + return patch + + +def apply_patch(mock_repo, rev, patch): + patch_path = f'/tmp/{rev}.mock.patch' + + with open(patch_path, 'w') as target: + target.write(patch) + print(f'wrote {patch_path}') + + call(f'git am -k --reject {patch_path}', cwd=mock_repo, shell=True) + + +def main(): + args = parse_args() + + if repo_state_bad(args.mock): + return + + cleanup_old_patches(args.mock) + + initial_cpython_rev = find_initial_cpython_rev() + + revs = cpython_revs_affecting_mock(args.cpython, initial_cpython_rev) + for rev in revs: + + if has_been_backported(args.mock, rev): + continue + + patch = extract_patch_for(args.cpython, rev) + patch = munge(rev, patch) + apply_patch(args.mock, rev, patch) + break + + +def parse_args(): + parser = ArgumentParser() + parser.add_argument('--cpython', default='../cpython') + parser.add_argument('--mock', default=abspath(dirname(__file__))) + return parser.parse_args() + + +if __name__ == '__main__': + main() diff --git a/docs/index.txt b/docs/index.txt index e870d362..7d96decc 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -159,9 +159,9 @@ Backporting process 4. Pull down the cPython git mirror: https://github.com/python/cpython.git 5. Export the new revisions since the ``Last sync`:: - revs=`cat lastsync.txt` + start=`cat lastsync.txt` rm migrate-export - git log --pretty="format:%H " $revs.. -- Lib/unittest/mock.py \ + git log --pretty="format:%H " $start.. -- Lib/unittest/mock.py \ Lib/unittest/test/testmock/ > migrate-revs tac migrate-revs > migrate-sorted-revs for rev in $(< migrate-sorted-revs); do From 3f2847803c68010cddca7b39289b2d873545e7bc Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Mon, 28 Mar 2016 00:30:02 +0300 Subject: [PATCH 028/388] Issue #25195: Fix a regression in mock.MagicMock _Call is a subclass of tuple (changeset 3603bae63c13 only works for classes) so we need to implement __ne__ ourselves. Patch by Andrew Plummer. Backports: ce913877e42b7fa03434c2e765ace891e0f5c4dc Signed-off-by: Chris Withers The standalone package already had a __ne__ implemented. --- NEWS | 4 ++++ mock/tests/testmock.py | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/NEWS b/NEWS index 21204186..d3e0c325 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,10 @@ Library ------- +- Issue #25195: Fix a regression in mock.MagicMock. _Call is a subclass of + tuple (changeset 3603bae63c13 only works for classes) so we need to + implement __ne__ ourselves. Patch by Andrew Plummer. + - Issue #26323: Add Mock.assert_called() and Mock.assert_called_once() methods to unittest.mock. Patch written by Amit Saha. diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 954593be..e981f2c4 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -328,6 +328,17 @@ def test_call_args_comparison(self): # an exception. See issue 24857. self.assertFalse(mock.call_args == "a long sequence") + + def test_calls_equal_with_any(self): + call1 = mock.call(mock.MagicMock()) + call2 = mock.call(mock.ANY) + + # Check that equality and non-equality is consistent even when + # comparing with mock.ANY + self.assertTrue(call1 == call2) + self.assertFalse(call1 != call2) + + def test_assert_called_with(self): mock = Mock() mock() @@ -343,6 +354,12 @@ def test_assert_called_with(self): mock.assert_called_with(1, 2, 3, a='fish', b='nothing') + def test_assert_called_with_any(self): + m = MagicMock() + m(MagicMock()) + m.assert_called_with(mock.ANY) + + def test_assert_called_with_function_spec(self): def f(a, b, c, d=None): pass From 8a5e0c3fb2bc2a90924e9e0d1f434403940a90b4 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sat, 27 Apr 2019 14:29:03 +0100 Subject: [PATCH 029/388] update backporting docs. --- docs/index.txt | 89 +++++++++++++++++++++++--------------------------- 1 file changed, 41 insertions(+), 48 deletions(-) diff --git a/docs/index.txt b/docs/index.txt index 7d96decc..f374f33f 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -69,7 +69,7 @@ The current version is |release|. Mock is stable and widely used. You can checkout the latest development version from GitHub repository with the following command: - ``git clone https://github.com/testing-cabal/mock`` + ``git clone https://github.com/testing-cabal/mock.git`` .. index:: pip @@ -78,12 +78,6 @@ You can install mock with pip: | ``pip install -U mock`` -Alternatively you can download the mock distribution from PyPI and after -unpacking run: - - ``python setup.py install`` - - .. index:: bug reports Bug Reports @@ -145,48 +139,47 @@ 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)``. +- ``isinstance`` checks in cPython to ``type`` need to check ``ClassTypes``. + Code calling ``obj.isidentifier`` needs to change to ``_isidentifier(obj)``. Backporting process ------------------- -1. Patch your git am with `my patch `_. -2. Install the applypatch-transform hook from tools/ to your .git hooks dir. -3. Configure a pre-applypatch hook to test at least all the cPython versions - we support on each patch that is applied. I use containers, and a sample - script is in tools/pre-applypatch. -4. Pull down the cPython git mirror: https://github.com/python/cpython.git -5. Export the new revisions since the ``Last sync`:: - - start=`cat lastsync.txt` - rm migrate-export - git log --pretty="format:%H " $start.. -- Lib/unittest/mock.py \ - Lib/unittest/test/testmock/ > migrate-revs - tac migrate-revs > migrate-sorted-revs - for rev in $(< migrate-sorted-revs); do - git format-patch -1 $rev -k --stdout >> migrate-export; - done - echo NEW SYNC POINT: $(git rev-parse HEAD) - -6. Import into mock:: - - git am -k --reject $path-to-cpython/migrate-export - - This will transform the patches automatically. Currently it will error - on every NEWS change as I haven't gotten around to making those patches - automatic. Fixup any errors that occur. When the patch is ready, do a ``git - add -u`` to update the index and then ``git am --continue`` to move onto - the next patch. If the patch is inappropriate e.g. the patch removing - __ne__ which would break older pythons, then either do ``git reset --hard; - git am --skip`` to discard any partially applied changes and skip over it, - or, if it has a NEWS entry thats worth preserving, edit it down to just - that, with a note such as we have for the ``__ne__`` patch, and continue on - from there. - - The goal is that every patch work at all times. - -7. After the import is complete, update this document with the new sync point. - -8. Push to a personal branch and propose a PR to the main repo. This will make - Travis-CI test it. If it works, push to the main repo. +1. Clone cpython and mock into the same directory, eg: + + .. code-block:: bash + + mkdir vcs + cd vcs + git clone https://github.com/python/cpython.git + git clone https://github.com/testing-cabal/mock.git + + Make sure they both on master and up to date! + +2. Create a branch in your ``mock`` clone and switch to it. + +3. Make sure you build a suitable virtualenv for Mock development + and activate it. For backporting, this should use Python 3.7+. + +4. Run ``backport.py``: + + .. code-block:: bash + + cd vcs/mock + python backport.py + + This will find the next cpython patch that needs to be applied, munge it + and attempt to apply it with ``git am``. + + If it succeeds, run the tests and/or push your branch up to a fork and + do a pull request into the master branch of the main repo to kick off + the continuous integration tests. + + If it fails, you'll have to manually work with what ``git status`` shows + to get the patch committed. + + If you have to make changes, please do a ``git commit --amend`` and add notes + about what needed doing below the ``Signed-off-by`` block. + + +5. Rinse and repeat until ``backport.py`` reports no more patches need applying. From b952ae408774a39a6a8224916965cbbfc29ca941 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sat, 27 Apr 2019 14:29:52 +0100 Subject: [PATCH 030/388] skip 1e8ee9b3808cd6c1a7a29c75115d1060a8ee877b as it requires no changes here. --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index 951fcfae..8c1bfcd4 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -2c2a4e63d794eb55e9163322ea11b9765e9e0db5 +1e8ee9b3808cd6c1a7a29c75115d1060a8ee877b From 2af4664bb8af3d416d26dba4a203f808230319ef Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sat, 27 Apr 2019 14:38:02 +0100 Subject: [PATCH 031/388] skip e437a10d15ddfd21d406e591acccf12ff443194e as it has no changes needed. --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index 8c1bfcd4..1e9cca56 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -1e8ee9b3808cd6c1a7a29c75115d1060a8ee877b +e437a10d15ddfd21d406e591acccf12ff443194e From 1a19afa054c22264e0c53b78e19ee453921aca9c Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Mon, 16 May 2016 15:22:01 +1200 Subject: [PATCH 032/388] Issue #26807: mock_open 'files' no longer error on readline at end of file. Patch from Yolanda Robla. Backports: 9549a3e3d4be2a15c222996abff8cb97180ee9be Signed-off-by: Chris Withers --- NEWS | 3 +++ mock/mock.py | 2 ++ mock/tests/testmock.py | 16 +++++++++++++++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index d3e0c325..c2bfcc96 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,9 @@ Library ------- +- Issue #26807: mock_open 'files' no longer error on readline at end of file. + Patch from Yolanda Robla. + - Issue #25195: Fix a regression in mock.MagicMock. _Call is a subclass of tuple (changeset 3603bae63c13 only works for classes) so we need to implement __ne__ ourselves. Patch by Andrew Plummer. diff --git a/mock/mock.py b/mock/mock.py index b0d5365f..eaca86be 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2490,6 +2490,8 @@ def _readline_side_effect(): yield handle.readline.return_value for line in _state[0]: yield line + while True: + yield type(read_data)() global file_spec diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index e981f2c4..f5e0b791 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -1503,7 +1503,21 @@ def test_mock_open_alter_readline(self): second = mopen().readline() 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') + h = _open('bar') + h.read() + self.assertEqual('', h.read()) + self.assertEqual('', h.read()) + self.assertEqual('', h.readline()) + self.assertEqual('', h.readline()) + self.assertEqual([], h.readlines()) + self.assertEqual([], h.readlines()) + + def test_mock_parents(self): for Klass in Mock, MagicMock: m = Klass() From eef871e31a430dad93ffdb6c8a86d29cb94cec29 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 2 Jun 2016 10:20:16 -0700 Subject: [PATCH 033/388] Issue #21271: Adds new keyword only parameters in reset_mock call We now have two keyword only parameters in the reset_mock function to selectively reset the return_value or the side_effects, or both. Backports: 9cd39a170b4e65bd17ba853e87134000523c055a Signed-off-by: Chris Withers --- NEWS | 2 ++ mock/mock.py | 7 ++++++- mock/tests/testmock.py | 18 ++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index c2bfcc96..6eb2d306 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,8 @@ Library ------- +- Issue #21271: New keyword only parameters in reset_mock call. + - Issue #26807: mock_open 'files' no longer error on readline at end of file. Patch from Yolanda Robla. diff --git a/mock/mock.py b/mock/mock.py index eaca86be..ad6041f9 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -637,7 +637,7 @@ def __set_side_effect(self, value): side_effect = property(__get_side_effect, __set_side_effect) - def reset_mock(self, visited=None): + 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 = [] @@ -652,6 +652,11 @@ def reset_mock(self, visited=None): self.call_args_list = _CallList() self.method_calls = _CallList() + if return_value: + self._mock_return_value = DEFAULT + if side_effect: + self._mock_side_effect = None + for child in self._mock_children.values(): if isinstance(child, _SpecState): continue diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index f5e0b791..d1abc438 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -1341,6 +1341,24 @@ def test_override_tuple_methods(self): self.assertEqual(m.method_calls[0], c) self.assertEqual(m.method_calls[1], i) + def test_reset_return_sideeffect(self): + m = Mock(return_value=10, side_effect=[2,3]) + m.reset_mock(return_value=True, side_effect=True) + self.assertIsInstance(m.return_value, Mock) + self.assertEqual(m.side_effect, None) + + def test_reset_return(self): + m = Mock(return_value=10, side_effect=[2,3]) + m.reset_mock(return_value=True) + self.assertIsInstance(m.return_value, Mock) + self.assertNotEqual(m.side_effect, None) + + def test_reset_sideeffect(self): + 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_mock_add_spec(self): class _One(object): one = 1 From b0b839f24ceef38a3642d891347fa5c17af34688 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sat, 27 Apr 2019 14:48:34 +0100 Subject: [PATCH 034/388] add note about changes needed to make patches work in the backport --- docs/index.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/index.txt b/docs/index.txt index f374f33f..dc6d940c 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -181,5 +181,8 @@ Backporting process If you have to make changes, please do a ``git commit --amend`` and add notes about what needed doing below the ``Signed-off-by`` block. + If you have to make changes because tests fail with an applied patch, please + make those changes in a followup commit and take note of the "Backporting rules" + above. 5. Rinse and repeat until ``backport.py`` reports no more patches need applying. From 1cd004f806e1e7fa63404e65d3de7ebd6291851e Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sat, 27 Apr 2019 14:55:23 +0100 Subject: [PATCH 035/388] clean up some old files that aren't needed anymore. --- .testr.conf | 4 ---- mock.wpr | 26 -------------------------- tools/applypatch-transform | 38 -------------------------------------- tools/pre-applypatch | 36 ------------------------------------ 4 files changed, 104 deletions(-) delete mode 100644 .testr.conf delete mode 100644 mock.wpr delete mode 100755 tools/applypatch-transform delete mode 100755 tools/pre-applypatch diff --git a/.testr.conf b/.testr.conf deleted file mode 100644 index 8a65628a..00000000 --- a/.testr.conf +++ /dev/null @@ -1,4 +0,0 @@ -[DEFAULT] -test_command=${PYTHON:-python} -m subunit.run discover . $LISTOPT $IDOPTION -test_id_option=--load-list $IDFILE -test_list_option=--list diff --git a/mock.wpr b/mock.wpr deleted file mode 100644 index e1ded971..00000000 --- a/mock.wpr +++ /dev/null @@ -1,26 +0,0 @@ -#!wing -#!version=4.0 -################################################################## -# Wing IDE project file # -################################################################## -[project attributes] -proj.directory-list = [{'dirloc': loc('.'), - 'excludes': [u'latex', - u'.hg', - u'.tox', - u'dist', - u'htmlcov', - u'extendmock.py', - u'__pycache__', - u'html', - u'build', - u'mock.egg-info', - u'tests/__pycache__', - u'.hgignore', - u'.hgtags'], - 'filter': '*', - 'include_hidden': 0, - 'recursive': 1, - 'watch_for_changes': 1}] -proj.file-type = 'shared' -testing.auto-test-file-specs = ('test*.py',) diff --git a/tools/applypatch-transform b/tools/applypatch-transform deleted file mode 100755 index 52fcd939..00000000 --- a/tools/applypatch-transform +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/sh -# -# An example hook script to transform a patch taken from an email -# by git am. -# -# The hook should exit with non-zero status after issuing an -# appropriate message if it wants to stop the commit. The hook is -# allowed to edit the patch file. -# -# To enable this hook, rename this file to "applypatch-transform". -# -# This example changes the path of Lib/unittest/mock.py to mock.py -# Lib/unittest/tests/testmock to tests and Misc/NEWS to NEWS, and -# finally skips any patches that did not alter mock.py or its tests. - -set -eux - -patch_path=$1 - -# Pull out mock.py -filterdiff --clean --strip 3 --addprefix=a/mock/ -i 'a/Lib/unittest/mock.py' -i 'b/Lib/unittest/mock.py' $patch_path > $patch_path.mock -# And the tests -filterdiff --clean --strip 5 --addprefix=a/mock/tests/ -i 'a/Lib/unittest/test/testmock/*.py' -i 'b/Lib/unittest/test/testmock/*.py' $patch_path > $patch_path.tests -# Lastly we want to pick up any NEWS entries. -filterdiff --strip 2 --addprefix=a/ -i a/Misc/NEWS -i b/Misc/NEWS $patch_path > $patch_path.NEWS -cp $patch_path $patch_path.orig -# bash -cat $patch_path.mock $patch_path.tests > $patch_path -filtered=$(cat $patch_path) -if [ -n "${filtered}" ]; then - cat $patch_path.NEWS >> $patch_path - exitcode=0 -else - exitcode=1 -fi - -rm $patch_path.mock $patch_path.tests $patch_path.NEWS -exit $exitcode diff --git a/tools/pre-applypatch b/tools/pre-applypatch deleted file mode 100755 index 90269a30..00000000 --- a/tools/pre-applypatch +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash -# -# An example hook script to verify what is about to be committed -# by applypatch from an e-mail message. -# -# The hook should exit with non-zero status after issuing an -# appropriate message if it wants to stop the commit. -# -# To enable this hook, rename this file to "pre-applypatch". - -set -eu - -#. git-sh-setup -echo "** in hook **" - -function test_version { - version=$1 - host=$(ls ~/.virtualenvs/mock-$version-* -d | sed -e "s/^.*mock-$version-//") - if [ -z "$host" ]; then - echo "No host found for $version" - return 1 - fi - echo testing $version in virtualenv mock-$version-$host on ssh host $host - ssh $host "cd work/mock && . ~/.virtualenvs/mock-$version-$host/bin/activate && pip install .[test] && unit2" -} - -find . -name "*.pyc" -exec rm "{}" \; - -test_version 2.7 -test_version 3.4 -test_version 3.5 -test_version 3.6 -test_version cpython -test_version pypy - -echo '** pre-apply complete and successful **' From 978840cda5f637699116cd326df7d18d64230b80 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sat, 27 Apr 2019 14:57:13 +0100 Subject: [PATCH 036/388] These can't be keyword-only until 2.7 support is dropped. --- mock/mock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock/mock.py b/mock/mock.py index ad6041f9..32cb7fd3 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -637,7 +637,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 = [] From 380d9e6062e558bdf5c27fa0c632049ecc6edc61 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 19 Jun 2016 18:30:43 +0300 Subject: [PATCH 037/388] Issue #23641: Added __getnewargs_ex__ to the list of special mock attributes. Backports: 5943ea76d529f9ea18c73a61e10c6f53bdcc864f Signed-off-by: Chris Withers --- mock/mock.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mock/mock.py b/mock/mock.py index 32cb7fd3..9e680ac5 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1848,6 +1848,7 @@ def _patch_stopall(): '__reduce__', '__reduce_ex__', '__getinitargs__', '__getnewargs__', '__getstate__', '__setstate__', '__getformat__', '__setformat__', '__repr__', '__dir__', '__subclasses__', '__format__', + '__getnewargs_ex__', } From 871b52660447c0a1d3ae48e21da21ded5a2e81b6 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sat, 27 Apr 2019 15:04:13 +0100 Subject: [PATCH 038/388] opportunistically update lastsync.txt --- backport.py | 8 +++++++- docs/index.txt | 3 +++ lastsync.txt | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/backport.py b/backport.py index 147e3e64..7719dc4d 100644 --- a/backport.py +++ b/backport.py @@ -1,6 +1,6 @@ import re from argparse import ArgumentParser -from os.path import dirname, abspath +from os.path import dirname, abspath, join from subprocess import check_output, call @@ -74,6 +74,11 @@ def apply_patch(mock_repo, rev, patch): call(f'git am -k --reject {patch_path}', cwd=mock_repo, shell=True) +def update_last_sync(mock_repo, rev): + with open(join(mock_repo, 'lastsync.txt'), 'w') as target: + target.write(rev+'\n') + print(f'update lastsync.txt to {rev}') + def main(): args = parse_args() @@ -88,6 +93,7 @@ def main(): for rev in revs: if has_been_backported(args.mock, rev): + update_last_sync(args.mock, rev) continue patch = extract_patch_for(args.cpython, rev) diff --git a/docs/index.txt b/docs/index.txt index dc6d940c..a7bc3a4c 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -186,3 +186,6 @@ Backporting process above. 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. diff --git a/lastsync.txt b/lastsync.txt index 1e9cca56..df3029e5 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -e437a10d15ddfd21d406e591acccf12ff443194e +5943ea76d529f9ea18c73a61e10c6f53bdcc864f From 2f356b28d42a1fd0057c9d8763d3a2cac2284165 Mon Sep 17 00:00:00 2001 From: Martin Panter Date: Mon, 11 Jul 2016 07:51:37 +0000 Subject: [PATCH 039/388] English spelling and grammar fixes Backports: 204bf0b9aecd221c33f3e0909f261411783acf1b Signed-off-by: Chris Withers --- mock/mock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock/mock.py b/mock/mock.py index 9e680ac5..51877cf2 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -866,7 +866,7 @@ def _format_mock_failure_message(self, args, kwargs): def _call_matcher(self, _call): """ - Given a call (or simply a (args, kwargs) tuple), return a + Given a call (or simply an (args, kwargs) tuple), return a comparison key suitable for matching with other calls. This is a best effort method which relies on the spec's signature, if available, or falls back on the arguments themselves. From 20f9d7510977b415b9f038e2795daae603dd7fed Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Sun, 7 Aug 2016 08:52:26 -0700 Subject: [PATCH 040/388] Issue #26750: unittest.mock.create_autospec() now works properly for subclasses of property() and other data descriptors. Backports: 9854789efec0c707fff871b32b2833f32b078fb3 Signed-off-by: Chris Withers The cpython patch removed two tests and replaced with one, I've just added the new test paranoidly, so we keep the old ones as the diff didn't apply cleanly. --- NEWS | 3 +++ mock/mock.py | 10 +++++++- mock/tests/testhelpers.py | 50 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 6eb2d306..7f522763 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,9 @@ Library ------- +- Issue #26750: unittest.mock.create_autospec() now works properly for + subclasses of property() and other data descriptors. + - Issue #21271: New keyword only parameters in reset_mock call. - Issue #26807: mock_open 'files' no longer error on readline at end of file. diff --git a/mock/mock.py b/mock/mock.py index 51877cf2..b2f5c756 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -154,12 +154,20 @@ 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 _is_data_descriptor(obj): + # Data descriptors are Properties, slots, getsets and C data members. + return ((hasattr(obj, '__set__') or hasattr(obj, '__del__')) and + hasattr(obj, '__get__')) + + def _get_signature_object(func, as_instance, eat_self): """ Given an arbitrary, possibly callable object, try to create a suitable @@ -2300,7 +2308,7 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, _kwargs.update(kwargs) Klass = MagicMock - if type(spec) in DescriptorTypes: + if _is_data_descriptor(spec): # descriptors don't have a spec # because we don't know what type they return _kwargs = {} diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index 2b2053f2..66dbb3f9 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -890,6 +890,56 @@ class Foo(object): 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): + self.value = value + + def __get__(self, obj, cls=None): + if obj is None: + return self + return self.value + + def __set__(self, obj, value): + pass + + class MyProperty(property): + pass + + class Foo(object): + __slots__ = ['slot'] + + @property + def prop(self): + return 3 + + @MyProperty + def subprop(self): + return 4 + + desc = Descriptor(42) + + foo = create_autospec(Foo) + + def check_data_descriptor(mock_attr): + # Data descriptors don't have a spec. + self.assertIsInstance(mock_attr, MagicMock) + mock_attr(1, 2, 3) + mock_attr.abc(4, 5, 6) + mock_attr.assert_called_once_with(1, 2, 3) + mock_attr.abc.assert_called_once_with(4, 5, 6) + + # property + check_data_descriptor(foo.prop) + # property subclass + check_data_descriptor(foo.subprop) + # class __slot__ + check_data_descriptor(foo.slot) + # plain data descriptor + check_data_descriptor(foo.desc) + + def test_autospec_on_bound_builtin_function(self): meth = six.create_bound_method(time.ctime, time.time()) self.assertIsInstance(meth(), str) From 76523d3afa7a111db946ad0bbb093244083cf62e Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sat, 27 Apr 2019 15:47:35 +0100 Subject: [PATCH 041/388] clarify this message --- backport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backport.py b/backport.py index 7719dc4d..c8eeda8b 100644 --- a/backport.py +++ b/backport.py @@ -32,7 +32,7 @@ def cpython_revs_affecting_mock(cpython_repo, start): f'-- Lib/unittest/mock.py Lib/unittest/test/testmock/', repo=cpython_repo).split() revs.reverse() - print(f'{len(revs)} patches to backport') + print(f'{len(revs)} patches that may need backporting') return revs From a7e2913ae2913d9ab954a317f95a5104553f23f7 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Mon, 15 Aug 2016 23:23:40 -0700 Subject: [PATCH 042/388] Issue #26750: use inspect.isdatadescriptor instead of our own _is_data_descriptor(). Backports: d4583d7fea9e3fbbc0a8f5333003938e358b5a58 Signed-off-by: Chris Withers --- mock/mock.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/mock/mock.py b/mock/mock.py index b2f5c756..3f9851c3 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -162,12 +162,6 @@ class _slotted(object): ) -def _is_data_descriptor(obj): - # Data descriptors are Properties, slots, getsets and C data members. - return ((hasattr(obj, '__set__') or hasattr(obj, '__del__')) and - hasattr(obj, '__get__')) - - def _get_signature_object(func, as_instance, eat_self): """ Given an arbitrary, possibly callable object, try to create a suitable @@ -2308,7 +2302,7 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, _kwargs.update(kwargs) Klass = MagicMock - if _is_data_descriptor(spec): + if inspect.isdatadescriptor(spec): # descriptors don't have a spec # because we don't know what type they return _kwargs = {} From 1d1762e75c52185ec8bf54c13d76674334c0bd98 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Tue, 30 Aug 2016 10:47:49 -0700 Subject: [PATCH 043/388] =?UTF-8?q?Issue=20#27895:=20=20Spelling=20fixes?= =?UTF-8?q?=20(Contributed=20by=20Ville=20Skytt=C3=A4).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backports: 15f44ab043b37c064d6891c7864205fed9fb0dd1 Signed-off-by: Chris Withers --- mock/tests/testcallable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock/tests/testcallable.py b/mock/tests/testcallable.py index c3cf2e5f..03c8929d 100644 --- a/mock/tests/testcallable.py +++ b/mock/tests/testcallable.py @@ -27,7 +27,7 @@ def test_non_callable(self): self.assertIn(mock.__class__.__name__, repr(mock)) - def test_heirarchy(self): + def test_hierarchy(self): self.assertTrue(issubclass(MagicMock, Mock)) self.assertTrue(issubclass(NonCallableMagicMock, NonCallableMock)) From 4f0c247ce0b63b1f34baec6da99f945e419aa6bf Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sat, 27 Apr 2019 17:16:58 +0100 Subject: [PATCH 044/388] empty file --- extendmock.py | 1 - 1 file changed, 1 deletion(-) delete mode 100644 extendmock.py diff --git a/extendmock.py b/extendmock.py deleted file mode 100644 index 0550d9fd..00000000 --- a/extendmock.py +++ /dev/null @@ -1 +0,0 @@ -# merged into mock.py in Mock 0.7 From f6f3830d44171d51942f9db90a3599dd3562a65e Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sat, 27 Apr 2019 17:33:45 +0100 Subject: [PATCH 045/388] skip 0be894b2f6ca17204922399d6982f0b8a9dc59a1, it has no changes needed here. --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index df3029e5..a27273d2 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -5943ea76d529f9ea18c73a61e10c6f53bdcc864f +0be894b2f6ca17204922399d6982f0b8a9dc59a1 From a336518f027106c4d45f40d4a9b73b188bf3e328 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sat, 27 Apr 2019 17:35:49 +0100 Subject: [PATCH 046/388] add the ability to skip an unneeded patch. --- backport.py | 21 +++++++++++++++++++++ docs/index.txt | 3 +++ 2 files changed, 24 insertions(+) diff --git a/backport.py b/backport.py index c8eeda8b..56dcbb34 100644 --- a/backport.py +++ b/backport.py @@ -79,9 +79,28 @@ def update_last_sync(mock_repo, rev): target.write(rev+'\n') print(f'update lastsync.txt to {rev}') + +def rev_from_mock_patch(text): + match = re.search('Backports: ([a-z0-9]+)', text) + return match.group(1) + + +def skip_current(mock_repo, reason): + text = git('am --show-current-patch', repo=mock_repo) + rev = rev_from_mock_patch(text) + git('am --abort', repo=mock_repo) + print(f'skipping {rev}') + update_last_sync(mock_repo, rev) + call(f'git commit -m "skip {rev}, {reason}" lastsync.txt', shell=True, cwd=mock_repo) + cleanup_old_patches(mock_repo) + + def main(): args = parse_args() + if args.skip_current: + return skip_current(args.mock, args.skip_reason) + if repo_state_bad(args.mock): return @@ -106,6 +125,8 @@ def parse_args(): parser = ArgumentParser() parser.add_argument('--cpython', default='../cpython') parser.add_argument('--mock', default=abspath(dirname(__file__))) + parser.add_argument('--skip-current', action='store_true') + parser.add_argument('--skip-reason', default='it has no changes needed here.') return parser.parse_args() diff --git a/docs/index.txt b/docs/index.txt index a7bc3a4c..efa128a3 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -178,6 +178,9 @@ Backporting process If it fails, you'll have to manually work with what ``git status`` shows to get the patch committed. + If it turns out that there's nothing that should be applied from the failed commit, + run ``python backport.py --skip-current``. + If you have to make changes, please do a ``git commit --amend`` and add notes about what needed doing below the ``Signed-off-by`` block. From 5ee50627fcc9112dce360b0f1ec7f701eca3ab77 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 28 Apr 2019 14:54:43 +0100 Subject: [PATCH 047/388] skip ac5084b6c760ff5e6469854373fe6c3c81804f87, code already in backport --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index a27273d2..acb35b76 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -0be894b2f6ca17204922399d6982f0b8a9dc59a1 +ac5084b6c760ff5e6469854373fe6c3c81804f87 From 8307e8504de439448edb99e3c77fe4c601a40ef8 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 28 Apr 2019 14:56:19 +0100 Subject: [PATCH 048/388] Backports: 161a4dd495dbf5cb12364e8f6e2d113cfd0633fc, skipped: code already in backport --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index acb35b76..fce21253 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -ac5084b6c760ff5e6469854373fe6c3c81804f87 +161a4dd495dbf5cb12364e8f6e2d113cfd0633fc From f9efc3e82452b7a7c936d85ff08eb9368a2f03eb Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 6 Jan 2017 18:15:51 +0100 Subject: [PATCH 049/388] Fix unittest.mock._Call: don't ignore name Issue #28961: Fix unittest.mock._Call helper: don't ignore the name parameter anymore. Patch written by Jiajun Huang. Backports: 84b6fb0eea29b3b28a1a11124526b01ec0c9d17a Signed-off-by: Chris Withers --- NEWS | 3 +++ mock/mock.py | 3 +-- mock/tests/testhelpers.py | 14 ++++++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 7f522763..539b843f 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,9 @@ Library ------- +- Issue #28961: Fix unittest.mock._Call helper: don't ignore the name parameter + anymore. Patch written by Jiajun Huang. + - Issue #26750: unittest.mock.create_autospec() now works properly for subclasses of property() and other data descriptors. diff --git a/mock/mock.py b/mock/mock.py index 3f9851c3..864366dd 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2109,9 +2109,8 @@ class _Call(tuple): If the _Call has no name then it will match any name. """ - def __new__(cls, value=(), name=None, parent=None, two=False, + def __new__(cls, value=(), name='', parent=None, two=False, from_kall=True): - name = '' args = () kwargs = {} _len = len(value) diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index 66dbb3f9..d402ca9d 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -315,6 +315,20 @@ def test_two_args_call(self): other_args = _Call(((1, 2), {'a': 3})) self.assertEqual(args, other_args) + def test_call_with_name(self): + self.assertEqual( + 'foo', + _Call((), 'foo')[0], + ) + self.assertEqual( + '', + _Call((('bar', 'barz'), ), )[0] + ) + self.assertEqual( + '', + _Call((('bar', 'barz'), {'hello': 'world'}), )[0] + ) + class SpecSignatureTest(unittest.TestCase): From 5a0e779b61ef3a87d6bc48bc3355a73f0deb523a Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 11 Jan 2017 20:13:03 +0200 Subject: [PATCH 050/388] Issue #20804: The unittest.mock.sentinel attributes now preserve their identity when they are copied or pickled. Backports: d9c956fb23f1c38c8050e9531ff5a77559f7f7af Signed-off-by: Chris Withers --- NEWS | 3 +++ mock/mock.py | 6 ++++++ mock/tests/testsentinel.py | 14 +++++++++++++- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 539b843f..bbd4624f 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,9 @@ Library ------- +- Issue #20804: The unittest.mock.sentinel attributes now preserve their + identity when they are copied or pickled. + - Issue #28961: Fix unittest.mock._Call helper: don't ignore the name parameter anymore. Patch written by Jiajun Huang. diff --git a/mock/mock.py b/mock/mock.py index 864366dd..7377cd2e 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -356,6 +356,9 @@ def __init__(self, name): def __repr__(self): return 'sentinel.%s' % self.name + def __reduce__(self): + return 'sentinel.%s' % self.name + class _Sentinel(object): """Access attributes to return a named object, usable as a sentinel.""" @@ -368,6 +371,9 @@ def __getattr__(self, name): raise AttributeError return self._sentinels.setdefault(name, _SentinelObject(name)) + def __reduce__(self): + return 'sentinel' + sentinel = _Sentinel() diff --git a/mock/tests/testsentinel.py b/mock/tests/testsentinel.py index 3253fa3c..afaa1940 100644 --- a/mock/tests/testsentinel.py +++ b/mock/tests/testsentinel.py @@ -3,7 +3,8 @@ # http://www.voidspace.org.uk/python/mock/ import unittest - +import copy +import pickle from mock import sentinel, DEFAULT @@ -28,6 +29,17 @@ def testBases(self): # If this doesn't raise an AttributeError then help(mock) is broken self.assertRaises(AttributeError, lambda: sentinel.__bases__) + 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) + + def testCopy(self): + self.assertIs(copy.copy(sentinel.whatever), sentinel.whatever) + self.assertIs(copy.deepcopy(sentinel.whatever), sentinel.whatever) + if __name__ == '__main__': unittest.main() From b8e23999c6fef91aa101c6318193484d555576c2 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 28 Apr 2019 15:12:53 +0100 Subject: [PATCH 051/388] python 2 has no subtest --- mock/tests/testsentinel.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mock/tests/testsentinel.py b/mock/tests/testsentinel.py index afaa1940..14114450 100644 --- a/mock/tests/testsentinel.py +++ b/mock/tests/testsentinel.py @@ -31,7 +31,6 @@ 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) From 637abcba241a69a1d9a8acf240855a6ed158cb64 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 28 Apr 2019 15:40:23 +0100 Subject: [PATCH 052/388] Serhiy's approach didn't work on Python 2 or 3.4 --- mock/mock.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mock/mock.py b/mock/mock.py index 7377cd2e..1a4c3e09 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -357,7 +357,11 @@ def __repr__(self): return 'sentinel.%s' % self.name def __reduce__(self): - return 'sentinel.%s' % self.name + return _unpickle_sentinel, (self.name, ) + + +def _unpickle_sentinel(name): + return getattr(sentinel, name) class _Sentinel(object): From d178d4446697fe043414b18970e0f2e76d9bd388 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 21 Jan 2017 23:12:58 +0200 Subject: [PATCH 053/388] Issue #28735: Fixed the comparison of mock.MagickMock with mock.ANY. Backports: 362f058a89437f82f112cda439bb40abe4ddb8c5 Signed-off-by: Chris Withers --- NEWS | 2 ++ mock/mock.py | 8 ++++++-- mock/tests/testmock.py | 17 ++++++++++++++--- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/NEWS b/NEWS index bbd4624f..07fcf53f 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,8 @@ Library ------- +- Issue #28735: Fixed the comparison of mock.MagickMock with mock.ANY. + - Issue #20804: The unittest.mock.sentinel attributes now preserve their identity when they are copied or pickled. diff --git a/mock/mock.py b/mock/mock.py index 1a4c3e09..7661ee29 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1918,14 +1918,18 @@ def __eq__(other): ret_val = self.__eq__._mock_return_value if ret_val is not DEFAULT: return ret_val - return self is other + if self is other: + return True + return NotImplemented return __eq__ def _get_ne(self): def __ne__(other): if self.__ne__._mock_return_value is not DEFAULT: return DEFAULT - return self is not other + if self is other: + return False + return NotImplemented return __ne__ def _get_iter(self): diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index d1abc438..bd4e7a13 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -330,13 +330,24 @@ def test_call_args_comparison(self): def test_calls_equal_with_any(self): - call1 = mock.call(mock.MagicMock()) - call2 = mock.call(mock.ANY) - # Check that equality and non-equality is consistent even when # comparing with mock.ANY + mm = mock.MagicMock() + self.assertTrue(mm == mm) + self.assertFalse(mm != mm) + self.assertFalse(mm == mock.MagicMock()) + self.assertTrue(mm != mock.MagicMock()) + self.assertTrue(mm == mock.ANY) + self.assertFalse(mm != mock.ANY) + self.assertTrue(mock.ANY == mm) + self.assertFalse(mock.ANY != mm) + + call1 = mock.call(mock.MagicMock()) + call2 = mock.call(mock.ANY) self.assertTrue(call1 == call2) self.assertFalse(call1 != call2) + self.assertTrue(call2 == call1) + self.assertFalse(call2 != call1) def test_assert_called_with(self): From 4f25a9bfab8d856d35182aae409a33d462a1c2c4 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 28 Apr 2019 15:57:58 +0100 Subject: [PATCH 054/388] Record skips as backports so we can ignore them if they crop up in the git log command we're using again. --- backport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backport.py b/backport.py index 56dcbb34..84ea8028 100644 --- a/backport.py +++ b/backport.py @@ -91,7 +91,7 @@ def skip_current(mock_repo, reason): git('am --abort', repo=mock_repo) print(f'skipping {rev}') update_last_sync(mock_repo, rev) - call(f'git commit -m "skip {rev}, {reason}" lastsync.txt', shell=True, cwd=mock_repo) + call(f'git commit -m "Backports: {rev}, skipped: {reason}" lastsync.txt', shell=True, cwd=mock_repo) cleanup_old_patches(mock_repo) From 27e6f17bb01c792b7d2debf1214e482dcc25032c Mon Sep 17 00:00:00 2001 From: Arne de Laat Date: Thu, 23 Feb 2017 15:57:25 +0100 Subject: [PATCH 055/388] bpo-28911: Clarify the behaviour of assert_called_once_with. (#251) Backports: 324c5d8ca6ed1c964d3b20e5762139ec65c7827c Signed-off-by: Chris Withers --- mock/mock.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mock/mock.py b/mock/mock.py index 7661ee29..c65be25b 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -949,8 +949,8 @@ def _error_message(cause): def assert_called_once_with(_mock_self, *args, **kwargs): - """assert that the mock was called exactly once and with the specified - arguments.""" + """assert that the mock was called exactly once and that that call was + with the specified arguments.""" self = _mock_self if not self.call_count == 1: msg = ("Expected '%s' to be called once. Called %s times." % From 431b268c6dea4cf7cd04cd21aa869d8b35532ad0 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Thu, 6 Oct 2016 14:31:23 -0700 Subject: [PATCH 056/388] Fixes issue28380: unittest.mock Mock autospec functions now properly support assert_called, assert_not_called, and assert_called_once. Backports: ac5084b6c760ff5e6469854373fe6c3c81804f87 Signed-off-by: Chris Withers Code was already present, including this commit to stop it showing up in backport attempts. --- NEWS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS b/NEWS index 07fcf53f..d69a9b4b 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,9 @@ Library ------- +- Issue #28380: unittest.mock Mock autospec functions now properly support + assert_called, assert_not_called, and assert_called_once. + - Issue #28735: Fixed the comparison of mock.MagickMock with mock.ANY. - Issue #20804: The unittest.mock.sentinel attributes now preserve their From 90058bfad73b7aa18df3bf83fa2a4e513440229b Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 28 Apr 2019 16:04:26 +0100 Subject: [PATCH 057/388] Backports: 0be894b2f6ca17204922399d6982f0b8a9dc59a1, skipped: Already handled before --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index fce21253..a27273d2 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -161a4dd495dbf5cb12364e8f6e2d113cfd0633fc +0be894b2f6ca17204922399d6982f0b8a9dc59a1 From fa64f8bfd458102ab0b7805896e0bfb080a50607 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Sun, 26 Feb 2017 15:04:11 +0300 Subject: [PATCH 058/388] bpo-28961: Address my comments from earlier code review (#305) Backports: 5aa3856b4f325457e8ec1ccf669369f543e1f6b5 Signed-off-by: Chris Withers --- mock/tests/testhelpers.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index d402ca9d..e7c42f69 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -316,18 +316,9 @@ def test_two_args_call(self): self.assertEqual(args, other_args) def test_call_with_name(self): - self.assertEqual( - 'foo', - _Call((), 'foo')[0], - ) - self.assertEqual( - '', - _Call((('bar', 'barz'), ), )[0] - ) - self.assertEqual( - '', - _Call((('bar', 'barz'), {'hello': 'world'}), )[0] - ) + self.assertEqual(_Call((), 'foo')[0], 'foo') + self.assertEqual(_Call((('bar', 'barz'),),)[0], '') + self.assertEqual(_Call((('bar', 'barz'), {'hello': 'world'}),)[0], '') class SpecSignatureTest(unittest.TestCase): From 3860c7975741f976627778ae5d9e88fe5d0434be Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 28 Apr 2019 16:07:55 +0100 Subject: [PATCH 059/388] Backports: 856cbcc12f2e4cca93af5dc7ed6bcea4dd942f10, skipped: code already present --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index a27273d2..65313964 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -0be894b2f6ca17204922399d6982f0b8a9dc59a1 +856cbcc12f2e4cca93af5dc7ed6bcea4dd942f10 From c21fd35196f595a03f8a3df75c35b5bfb74808b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Thu, 3 Aug 2017 09:00:59 +0300 Subject: [PATCH 060/388] Spelling fixes (#2902) Backports: 49b2734bf12dc1cda80fd73d3ec8896ae3e362f2 Signed-off-by: Chris Withers --- mock/tests/testmagicmethods.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock/tests/testmagicmethods.py b/mock/tests/testmagicmethods.py index eb7eacef..8c6d235d 100644 --- a/mock/tests/testmagicmethods.py +++ b/mock/tests/testmagicmethods.py @@ -301,7 +301,7 @@ def test_magicmock(self): for entry in _magics: self.assertTrue(hasattr(mock, entry)) - self.assertFalse(hasattr(mock, '__imaginery__')) + self.assertFalse(hasattr(mock, '__imaginary__')) def test_magic_mock_equality(self): From f4bc0814cb05b19871c1f49b215e6f0189b8858a Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 28 Apr 2019 17:05:38 +0100 Subject: [PATCH 061/388] put news items in a .d directory for now. --- backport.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backport.py b/backport.py index 84ea8028..baeef374 100644 --- a/backport.py +++ b/backport.py @@ -59,6 +59,7 @@ def munge(rev, patch): ('(a|b)/Lib/unittest/mock.py', r'\1/mock/mock.py'), ('(a|b)/Lib/unittest/test/testmock/(.+)', r'\1/mock/tests/\2'), ('(a|b)/Misc/NEWS', r'\1/NEWS'), + ('(a|b)/NEWS.d/next/Library/(.+\.rst)', r'\1/NEWS.d/\2'), ): patch = re.sub(pattern, sub, patch) return patch From 8d313cc2a00f41f26c47266b16fb3ef279c3c488 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 28 Apr 2019 17:05:44 +0100 Subject: [PATCH 062/388] latest sync point --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index 65313964..b20cb50d 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -856cbcc12f2e4cca93af5dc7ed6bcea4dd942f10 +49b2734bf12dc1cda80fd73d3ec8896ae3e362f2 From 565b044c681717702595595cc38f67e8f652b6df Mon Sep 17 00:00:00 2001 From: Mario Corchero Date: Tue, 17 Oct 2017 12:35:11 +0100 Subject: [PATCH 063/388] bpo-30541: Add new method to seal mocks (GH61923) The new method allows the developer to control when to stop the feature of mocks that automagically creates new mocks when accessing an attribute that was not declared before Backports: 552be9d7e64f91b8e4ba5b29cd5dcc442d56f92c Signed-off-by: Mario Corchero Signed-off-by: Chris Withers --- .../2017-10-17-12-04-37.bpo-30541.q3BM6C.rst | 2 + mock/mock.py | 43 ++++- mock/tests/testsealable.py | 181 ++++++++++++++++++ 3 files changed, 224 insertions(+), 2 deletions(-) create mode 100644 NEWS.d/2017-10-17-12-04-37.bpo-30541.q3BM6C.rst create mode 100644 mock/tests/testsealable.py diff --git a/NEWS.d/2017-10-17-12-04-37.bpo-30541.q3BM6C.rst b/NEWS.d/2017-10-17-12-04-37.bpo-30541.q3BM6C.rst new file mode 100644 index 00000000..7eb5e16f --- /dev/null +++ b/NEWS.d/2017-10-17-12-04-37.bpo-30541.q3BM6C.rst @@ -0,0 +1,2 @@ +Add new function to seal a mock and prevent the automatically creation of +child mocks. Patch by Mario Corchero. diff --git a/mock/mock.py b/mock/mock.py index c65be25b..b3ef43fb 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -50,6 +50,7 @@ 'NonCallableMagicMock', 'mock_open', 'PropertyMock', + 'seal', ) @@ -513,6 +514,7 @@ def __init__( __dict__['_mock_name'] = name __dict__['_mock_new_name'] = _new_name __dict__['_mock_new_parent'] = _new_parent + __dict__['_mock_sealed'] = False if spec_set is not None: spec = spec_set @@ -739,7 +741,7 @@ def __getattr__(self, name): return result - def __repr__(self): + def _extract_mock_name(self): _name_list = [self._mock_new_name] _parent = self._mock_new_parent last = self @@ -769,7 +771,10 @@ def __repr__(self): if _name_list[1] not in ('()', '().'): _first += '.' _name_list[0] = _first - name = ''.join(_name_list) + return ''.join(_name_list) + + def __repr__(self): + name = self._extract_mock_name() name_string = '' if name not in ('mock', 'mock.'): @@ -839,6 +844,11 @@ def __setattr__(self, name, value): else: if _check_and_set_parent(self, value, name, name): self._mock_children[name] = value + + if self._mock_sealed and not hasattr(self, name): + mock_name = f'{self._extract_mock_name()}.{name}' + raise AttributeError(f'Cannot set {mock_name}') + return object.__setattr__(self, name, value) @@ -1026,6 +1036,12 @@ def _get_child_mock(self, **kw): klass = Mock else: klass = _type.__mro__[1] + + if self._mock_sealed: + attribute = "." + kw["name"] if "name" in kw else "()" + mock_name = self._extract_mock_name() + attribute + raise AttributeError(mock_name) + return klass(**kw) @@ -2571,3 +2587,26 @@ def __get__(self, obj, obj_type): return self() def __set__(self, obj, val): self(val) + + +def seal(mock): + """Disable the automatic generation of "submocks" + + Given an input Mock, seals it to ensure no further mocks will be generated + when accessing an attribute that was not already defined. + + Submocks are defined as all mocks which were created DIRECTLY from the + parent. If a mock is assigned to an attribute of an existing mock, + it is not considered a submock. + + """ + mock._mock_sealed = True + for attr in dir(mock): + try: + m = getattr(mock, attr) + except AttributeError: + continue + if not isinstance(m, NonCallableMock): + continue + if m._mock_new_parent is mock: + seal(m) diff --git a/mock/tests/testsealable.py b/mock/tests/testsealable.py new file mode 100644 index 00000000..0e72b324 --- /dev/null +++ b/mock/tests/testsealable.py @@ -0,0 +1,181 @@ +import unittest +from unittest import mock + + +class SampleObject: + def __init__(self): + self.attr_sample1 = 1 + self.attr_sample2 = 1 + + def method_sample1(self): + pass + + def method_sample2(self): + pass + + +class TestSealable(unittest.TestCase): + + def test_attributes_return_more_mocks_by_default(self): + m = mock.Mock() + + self.assertIsInstance(m.test, mock.Mock) + self.assertIsInstance(m.test(), mock.Mock) + self.assertIsInstance(m.test().test2(), mock.Mock) + + def test_new_attributes_cannot_be_accessed_on_seal(self): + m = mock.Mock() + + mock.seal(m) + with self.assertRaises(AttributeError): + m.test + with self.assertRaises(AttributeError): + m() + + def test_new_attributes_cannot_be_set_on_seal(self): + m = mock.Mock() + + mock.seal(m) + with self.assertRaises(AttributeError): + m.test = 1 + + def test_existing_attributes_can_be_set_on_seal(self): + m = mock.Mock() + m.test.test2 = 1 + + mock.seal(m) + m.test.test2 = 2 + self.assertEqual(m.test.test2, 2) + + def test_new_attributes_cannot_be_set_on_child_of_seal(self): + m = mock.Mock() + m.test.test2 = 1 + + mock.seal(m) + with self.assertRaises(AttributeError): + m.test.test3 = 1 + + def test_existing_attributes_allowed_after_seal(self): + m = mock.Mock() + + m.test.return_value = 3 + + mock.seal(m) + self.assertEqual(m.test(), 3) + + def test_initialized_attributes_allowed_after_seal(self): + m = mock.Mock(test_value=1) + + mock.seal(m) + self.assertEqual(m.test_value, 1) + + def test_call_on_sealed_mock_fails(self): + m = mock.Mock() + + mock.seal(m) + with self.assertRaises(AttributeError): + m() + + def test_call_on_defined_sealed_mock_succeeds(self): + m = mock.Mock(return_value=5) + + mock.seal(m) + self.assertEqual(m(), 5) + + def test_seals_recurse_on_added_attributes(self): + m = mock.Mock() + + m.test1.test2().test3 = 4 + + mock.seal(m) + self.assertEqual(m.test1.test2().test3, 4) + with self.assertRaises(AttributeError): + m.test1.test2().test4 + with self.assertRaises(AttributeError): + m.test1.test3 + + def test_seals_recurse_on_magic_methods(self): + m = mock.MagicMock() + + m.test1.test2["a"].test3 = 4 + m.test1.test3[2:5].test3 = 4 + + mock.seal(m) + self.assertEqual(m.test1.test2["a"].test3, 4) + self.assertEqual(m.test1.test2[2:5].test3, 4) + with self.assertRaises(AttributeError): + m.test1.test2["a"].test4 + with self.assertRaises(AttributeError): + m.test1.test3[2:5].test4 + + def test_seals_dont_recurse_on_manual_attributes(self): + m = mock.Mock(name="root_mock") + + m.test1.test2 = mock.Mock(name="not_sealed") + m.test1.test2.test3 = 4 + + mock.seal(m) + self.assertEqual(m.test1.test2.test3, 4) + m.test1.test2.test4 # Does not raise + m.test1.test2.test4 = 1 # Does not raise + + def test_integration_with_spec_att_definition(self): + """You are not restricted when using mock with spec""" + m = mock.Mock(SampleObject) + + m.attr_sample1 = 1 + m.attr_sample3 = 3 + + mock.seal(m) + self.assertEqual(m.attr_sample1, 1) + self.assertEqual(m.attr_sample3, 3) + with self.assertRaises(AttributeError): + m.attr_sample2 + + def test_integration_with_spec_method_definition(self): + """You need to defin the methods, even if they are in the spec""" + m = mock.Mock(SampleObject) + + m.method_sample1.return_value = 1 + + mock.seal(m) + self.assertEqual(m.method_sample1(), 1) + with self.assertRaises(AttributeError): + m.method_sample2() + + def test_integration_with_spec_method_definition_respects_spec(self): + """You cannot define methods out of the spec""" + m = mock.Mock(SampleObject) + + with self.assertRaises(AttributeError): + m.method_sample3.return_value = 3 + + def test_sealed_exception_has_attribute_name(self): + m = mock.Mock() + + mock.seal(m) + with self.assertRaises(AttributeError) as cm: + m.SECRETE_name + self.assertIn("SECRETE_name", str(cm.exception)) + + def test_attribute_chain_is_maintained(self): + m = mock.Mock(name="mock_name") + m.test1.test2.test3.test4 + + mock.seal(m) + with self.assertRaises(AttributeError) as cm: + m.test1.test2.test3.test4.boom + self.assertIn("mock_name.test1.test2.test3.test4.boom", str(cm.exception)) + + def test_call_chain_is_maintained(self): + m = mock.Mock() + m.test1().test2.test3().test4 + + mock.seal(m) + with self.assertRaises(AttributeError) as cm: + m.test1().test2.test3().test4() + self.assertIn("mock.test1().test2.test3().test4", str(cm.exception)) + + +if __name__ == "__main__": + unittest.main() From 18794433d33c35017a300fd40ac30afbc9dbb95a Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 28 Apr 2019 19:41:05 +0100 Subject: [PATCH 064/388] paranoid regex change --- backport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backport.py b/backport.py index baeef374..afe87d48 100644 --- a/backport.py +++ b/backport.py @@ -57,7 +57,7 @@ def munge(rev, patch): for pattern, sub in ( ('(a|b)/Lib/unittest/mock.py', r'\1/mock/mock.py'), - ('(a|b)/Lib/unittest/test/testmock/(.+)', r'\1/mock/tests/\2'), + (r'(a|b)/Lib/unittest/test/testmock/(\S+)', r'\1/mock/tests/\2'), ('(a|b)/Misc/NEWS', r'\1/NEWS'), ('(a|b)/NEWS.d/next/Library/(.+\.rst)', r'\1/NEWS.d/\2'), ): From 28e756644fa17f15beeee4011154e7656af7f49a Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 28 Apr 2019 19:41:23 +0100 Subject: [PATCH 065/388] only include stuff we're interested in when applying the patch --- backport.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backport.py b/backport.py index afe87d48..d2a276f0 100644 --- a/backport.py +++ b/backport.py @@ -72,7 +72,10 @@ def apply_patch(mock_repo, rev, patch): target.write(patch) print(f'wrote {patch_path}') - call(f'git am -k --reject {patch_path}', cwd=mock_repo, shell=True) + call(f'git am -k ' + f'--include "mock/*" --include NEWS --include "NEWS.d/*" ' + f'--reject {patch_path} ', + cwd=mock_repo, shell=True) def update_last_sync(mock_repo, rev): From d4469c6bef0b55040eefa6c7c1850cade659b320 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 28 Apr 2019 19:44:11 +0100 Subject: [PATCH 066/388] fixup import --- mock/tests/testsealable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock/tests/testsealable.py b/mock/tests/testsealable.py index 0e72b324..bd271421 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: From 3d7869b84e1a2d427df2b71cf7e22b1e9804afb7 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 28 Apr 2019 19:44:18 +0100 Subject: [PATCH 067/388] no f-strings in py2. --- mock/mock.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mock/mock.py b/mock/mock.py index b3ef43fb..60991398 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -846,8 +846,8 @@ def __setattr__(self, name, value): self._mock_children[name] = value if self._mock_sealed and not hasattr(self, name): - mock_name = f'{self._extract_mock_name()}.{name}' - raise AttributeError(f'Cannot set {mock_name}') + mock_name = self._extract_mock_name()+'.'+name + raise AttributeError('Cannot set '+mock_name) return object.__setattr__(self, name, value) From 6b7acf250c888ba665986e06e3068b7d2f683483 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 14 Dec 2017 14:04:53 +0300 Subject: [PATCH 068/388] bpo-32297: Few misspellings found in Python source code comments. (#4803) * Fix multiple typos in code comments * Add spacing in comments (test_logging.py, test_math.py) * Fix spaces at the beginning of comments in test_logging.py Backports: 53f7a7c2814fbfd8a29200926601a32fa48bacb3 Signed-off-by: Chris Withers --- mock/tests/testmagicmethods.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock/tests/testmagicmethods.py b/mock/tests/testmagicmethods.py index 8c6d235d..15517354 100644 --- a/mock/tests/testmagicmethods.py +++ b/mock/tests/testmagicmethods.py @@ -514,7 +514,7 @@ def test_divmod_and_rdivmod(self): self.assertIsInstance(bar_direct, MagicMock) # http://bugs.python.org/issue23310 - # Check if you can change behaviour of magic methds in MagicMock init + # Check if you can change behaviour of magic methods in MagicMock init def test_magic_in_initialization(self): m = MagicMock(**{'__str__.return_value': "12"}) self.assertEqual(str(m), "12") From 56cf6cba6d08338121043bd31134123ae4775ec6 Mon Sep 17 00:00:00 2001 From: John Reese Date: Tue, 22 May 2018 13:01:10 -0700 Subject: [PATCH 069/388] bpo-33516: Add support for __round__ in MagicMock (GH-6880) unittest.mock.MagicMock now supports the __round__() magic method. Backports: 6c4fab0f4b95410a1a964a75dcdd953697eff089 Signed-off-by: Chris Withers --- NEWS.d/2018-05-15-17-06-42.bpo-33516.ZzARe4.rst | 1 + mock/mock.py | 2 +- mock/tests/testmagicmethods.py | 5 +++++ 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 NEWS.d/2018-05-15-17-06-42.bpo-33516.ZzARe4.rst diff --git a/NEWS.d/2018-05-15-17-06-42.bpo-33516.ZzARe4.rst b/NEWS.d/2018-05-15-17-06-42.bpo-33516.ZzARe4.rst new file mode 100644 index 00000000..77b1428f --- /dev/null +++ b/NEWS.d/2018-05-15-17-06-42.bpo-33516.ZzARe4.rst @@ -0,0 +1 @@ +:class:`unittest.mock.MagicMock` now supports the ``__round__`` magic method. diff --git a/mock/mock.py b/mock/mock.py index 60991398..9f770d06 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1850,7 +1850,7 @@ def _patch_stopall(): # because there is no idivmod "divmod rdivmod neg pos abs invert " "complex int float index " - "trunc floor ceil " + "round trunc floor ceil " ) numerics = ( diff --git a/mock/tests/testmagicmethods.py b/mock/tests/testmagicmethods.py index 15517354..8ff06f06 100644 --- a/mock/tests/testmagicmethods.py +++ b/mock/tests/testmagicmethods.py @@ -11,6 +11,7 @@ unicode = str long = int +import math import sys import textwrap import unittest @@ -329,6 +330,10 @@ def test_magicmock_defaults(self): 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__()) + self.assertEqual(math.floor(mock), mock.__floor__()) + self.assertEqual(math.ceil(mock), mock.__ceil__()) if six.PY2: self.assertEqual(oct(mock), '1') else: From 934177ab629e7fe88525cfed362dc6799a28e30b Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 28 Apr 2019 20:13:16 +0100 Subject: [PATCH 070/388] In Python 2 round() and friends use __float__ first. --- mock/tests/testmagicmethods.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/mock/tests/testmagicmethods.py b/mock/tests/testmagicmethods.py index 8ff06f06..1056b2a5 100644 --- a/mock/tests/testmagicmethods.py +++ b/mock/tests/testmagicmethods.py @@ -330,10 +330,16 @@ def test_magicmock_defaults(self): 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__()) - self.assertEqual(math.floor(mock), mock.__floor__()) - self.assertEqual(math.ceil(mock), mock.__ceil__()) + 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: From b4579b9ce532e58c7cc9eab2f1ca426697a6a35f Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 28 Apr 2019 20:14:29 +0100 Subject: [PATCH 071/388] Backports: 9d6d06e8065d45f375f4a80e2d7e13b032da1f5b, skipped: it has no changes needed here. --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index b20cb50d..29f2b861 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -49b2734bf12dc1cda80fd73d3ec8896ae3e362f2 +9d6d06e8065d45f375f4a80e2d7e13b032da1f5b From 94c3c577464af973ace86716bd0957d88d6740cb Mon Sep 17 00:00:00 2001 From: davidair Date: Fri, 17 Aug 2018 15:09:58 -0400 Subject: [PATCH 072/388] Improve error message when mock.assert_has_calls fails (GH-8205) This makes the assertion error message more useful, aiding debugging. Thanks @davidair! Backports: 2b32da2fea1f077bb07a175f97ad8241e5605169 Signed-off-by: Chris Withers --- NEWS.d/next/Tests/2018-07-10-18-53-46.bpo-0.UBQJBc.rst | 1 + mock/mock.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 NEWS.d/next/Tests/2018-07-10-18-53-46.bpo-0.UBQJBc.rst diff --git a/NEWS.d/next/Tests/2018-07-10-18-53-46.bpo-0.UBQJBc.rst b/NEWS.d/next/Tests/2018-07-10-18-53-46.bpo-0.UBQJBc.rst new file mode 100644 index 00000000..9d826776 --- /dev/null +++ b/NEWS.d/next/Tests/2018-07-10-18-53-46.bpo-0.UBQJBc.rst @@ -0,0 +1 @@ +Improved an error message when mock assert_has_calls fails. diff --git a/mock/mock.py b/mock/mock.py index 9f770d06..2bf8c88d 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1000,7 +1000,9 @@ def assert_has_calls(self, calls, any_order=False): not_found.append(kall) if not_found: six.raise_from(AssertionError( - '{!r} not all found in call list'.format(tuple(not_found)) + '%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 73f6eed0d6867299fa2543b88a07cd8f12198361 Mon Sep 17 00:00:00 2001 From: Tony Flury Date: Wed, 12 Sep 2018 23:21:16 +0100 Subject: [PATCH 073/388] bpo-32933: Implement __iter__ method on mock_open() (GH-5974) Backports: 2087023fdec2c89070bd14f384a3c308c548a94a Signed-off-by: Chris Withers --- NEWS.d/2018-04-30-22-43-31.bpo-32933.M3iI_y.rst | 2 ++ mock/mock.py | 9 ++++++--- mock/tests/testmock.py | 10 ++++++++++ mock/tests/testwith.py | 15 +++++++++++++++ 4 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 NEWS.d/2018-04-30-22-43-31.bpo-32933.M3iI_y.rst diff --git a/NEWS.d/2018-04-30-22-43-31.bpo-32933.M3iI_y.rst b/NEWS.d/2018-04-30-22-43-31.bpo-32933.M3iI_y.rst new file mode 100644 index 00000000..4de7a8f9 --- /dev/null +++ b/NEWS.d/2018-04-30-22-43-31.bpo-32933.M3iI_y.rst @@ -0,0 +1,2 @@ +:func:`unittest.mock.mock_open` now supports iteration over the file +contents. Patch by Tony Flury. diff --git a/mock/mock.py b/mock/mock.py index 2bf8c88d..1c2159fb 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2524,14 +2524,16 @@ def _read_side_effect(*args, **kwargs): return type(read_data)().join(_state[0]) def _readline_side_effect(): + yield from _iter_side_effect() + while True: + yield type(read_data)() + + def _iter_side_effect(): if handle.readline.return_value is not None: while True: yield handle.readline.return_value for line in _state[0]: yield line - while True: - yield type(read_data)() - global file_spec if file_spec is None: @@ -2559,6 +2561,7 @@ def _readline_side_effect(): _state[1] = _readline_side_effect() handle.readline.side_effect = _state[1] handle.readlines.side_effect = _readlines_side_effect + handle.__iter__.side_effect = _iter_side_effect def reset_data(*args, **kwargs): _state[0] = _iterate_read_data(read_data) diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index bd4e7a13..2f2c4c23 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -1514,6 +1514,16 @@ def test_mock_open_reuse_issue_21750(self): f2_data = f2.read() self.assertEqual(f1_data, f2_data) + def test_mock_open_dunder_iter_issue(self): + # Test dunder_iter method generates the expected result and + # consumes the iterator. + mocked_open = mock.mock_open(read_data='Remarkable\nNorwegian Blue') + f1 = mocked_open('a-name') + lines = [line for line in f1] + self.assertEqual(lines[0], 'Remarkable\n') + self.assertEqual(lines[1], 'Norwegian Blue') + self.assertEqual(list(f1), []) + 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/testwith.py b/mock/tests/testwith.py index ad340ab5..0297d980 100644 --- a/mock/tests/testwith.py +++ b/mock/tests/testwith.py @@ -193,6 +193,7 @@ def test_read_data(self): def test_readline_data(self): # Check that readline will return all the lines from the fake file + # And that once fully consumed, readline will return an empty string. mock = mock_open(read_data='foo\nbar\nbaz\n') with patch('%s.open' % __name__, mock, create=True): h = open('bar') @@ -202,6 +203,7 @@ def test_readline_data(self): self.assertEqual(line1, 'foo\n') self.assertEqual(line2, 'bar\n') self.assertEqual(line3, 'baz\n') + self.assertEqual(h.readline(), '') # Check that we properly emulate a file that doesn't end in a newline mock = mock_open(read_data='foo') @@ -209,6 +211,19 @@ def test_readline_data(self): h = open('bar') result = h.readline() self.assertEqual(result, 'foo') + self.assertEqual(h.readline(), '') + + + def test_dunder_iter_data(self): + # Check that dunder_iter will return all the lines from the fake file. + mock = mock_open(read_data='foo\nbar\nbaz\n') + with patch('%s.open' % __name__, mock, create=True): + h = open('bar') + lines = [l for l in h] + self.assertEqual(lines[0], 'foo\n') + self.assertEqual(lines[1], 'bar\n') + self.assertEqual(lines[2], 'baz\n') + self.assertEqual(h.readline(), '') def test_readlines_data(self): From 3008b82265f3d00d1d8db457d3f5891b949ea4bc Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 28 Apr 2019 20:19:23 +0100 Subject: [PATCH 074/388] No 'yield from' in Python 2. --- mock/mock.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mock/mock.py b/mock/mock.py index 1c2159fb..2815af5c 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2524,7 +2524,8 @@ def _read_side_effect(*args, **kwargs): return type(read_data)().join(_state[0]) def _readline_side_effect(): - yield from _iter_side_effect() + for item in _iter_side_effect(): + yield item while True: yield type(read_data)() From d42410a09aeac50d5bb8a84bc1ab97dfd70ce909 Mon Sep 17 00:00:00 2001 From: Mario Corchero Date: Fri, 19 Oct 2018 22:57:37 +0100 Subject: [PATCH 075/388] unittest.mock doc: Fix references to recursive seal of Mocks (GH-9028) The docs in `library/unittest.mock` have been updated to remove confusing terms about submock and be explicit about the behavior expected. Backports: 96200eb2ffcda05de14099cf23f60d5091366e3e Signed-off-by: Chris Withers --- mock/mock.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/mock/mock.py b/mock/mock.py index 2815af5c..a414f3aa 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2596,15 +2596,14 @@ def __set__(self, obj, val): def seal(mock): - """Disable the automatic generation of "submocks" + """Disable the automatic generation of child mocks. Given an input Mock, seals it to ensure no further mocks will be generated when accessing an attribute that was not already defined. - Submocks are defined as all mocks which were created DIRECTLY from the - parent. If a mock is assigned to an attribute of an existing mock, - it is not considered a submock. - + The operation recursively seals the mock passed in, meaning that + the mock itself, any mocks generated by accessing one of its attributes, + and all assigned mocks without a name or spec will be sealed. """ mock._mock_sealed = True for attr in dir(mock): From 55971296dc33650318a6397b21d4c4296a8dbca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20Be=CC=81langer?= Date: Thu, 25 Oct 2018 14:48:58 -0700 Subject: [PATCH 076/388] bpo-35022: unittest.mock.MagicMock now also supports __fspath__ (GH-9960) The MagicMock class supports many magic methods, but not __fspath__. To ease testing with modules such as os.path, this function is now supported by default. Backports: 6c83d9f4a72905d968418bef670bb3091d2744db Signed-off-by: Chris Withers Needed some re-working as this can only work and apply on Python 3 --- NEWS.d/2018-10-18-17-57-28.bpo-35022.KeEF4T.rst | 2 ++ mock/mock.py | 3 ++- mock/tests/testmagicmethods.py | 12 ++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 NEWS.d/2018-10-18-17-57-28.bpo-35022.KeEF4T.rst diff --git a/NEWS.d/2018-10-18-17-57-28.bpo-35022.KeEF4T.rst b/NEWS.d/2018-10-18-17-57-28.bpo-35022.KeEF4T.rst new file mode 100644 index 00000000..426be70c --- /dev/null +++ b/NEWS.d/2018-10-18-17-57-28.bpo-35022.KeEF4T.rst @@ -0,0 +1,2 @@ +:class:`unittest.mock.MagicMock` now supports the ``__fspath__`` method +(from :class:`os.PathLike`). diff --git a/mock/mock.py b/mock/mock.py index a414f3aa..6e790f12 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1864,7 +1864,7 @@ def _patch_stopall(): right = ' '.join('r%s' % n for n in numerics.split()) extra = '' if six.PY3: - extra = 'bool next ' + extra = 'bool next fspath ' else: extra = 'unicode long nonzero oct hex truediv rtruediv ' @@ -1909,6 +1909,7 @@ def method(self, *args, **kw): '__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)), } _return_values = { diff --git a/mock/tests/testmagicmethods.py b/mock/tests/testmagicmethods.py index 1056b2a5..705b9d18 100644 --- a/mock/tests/testmagicmethods.py +++ b/mock/tests/testmagicmethods.py @@ -12,6 +12,7 @@ long = int import math +import os import sys import textwrap import unittest @@ -361,6 +362,17 @@ def test_non_default_magic_methods(self): self.assertEqual(mock, object()) + def test_magic_methods_fspath(self): + mock = MagicMock() + if six.PY2: + 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() + + def test_magic_methods_and_spec(self): class Iterable(object): def __iter__(self): From 6065cab480b753bde660963dc122830e82ebb8ec Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 28 Apr 2019 20:35:08 +0100 Subject: [PATCH 077/388] fspath added in 3.6 --- lastsync.txt | 2 +- mock/mock.py | 4 +++- mock/tests/testmagicmethods.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lastsync.txt b/lastsync.txt index 29f2b861..7888ac4b 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -9d6d06e8065d45f375f4a80e2d7e13b032da1f5b +96200eb2ffcda05de14099cf23f60d5091366e3e diff --git a/mock/mock.py b/mock/mock.py index 6e790f12..3e4f6ea1 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1864,7 +1864,9 @@ def _patch_stopall(): right = ' '.join('r%s' % n for n in numerics.split()) extra = '' if six.PY3: - extra = 'bool next fspath ' + extra = 'bool next ' + if sys.version_info >= (3, 6): + extra += 'fspath ' else: extra = 'unicode long nonzero oct hex truediv rtruediv ' diff --git a/mock/tests/testmagicmethods.py b/mock/tests/testmagicmethods.py index 705b9d18..d3d2d7c5 100644 --- a/mock/tests/testmagicmethods.py +++ b/mock/tests/testmagicmethods.py @@ -364,7 +364,7 @@ def test_non_default_magic_methods(self): def test_magic_methods_fspath(self): mock = MagicMock() - if six.PY2: + if sys.version_info < (3, 6): self.assertRaises(AttributeError, lambda: mock.__fspath__) else: expected_path = mock.__fspath__() From 57ef3bb5a48ccc549563c42f4a3f1f105ab49c29 Mon Sep 17 00:00:00 2001 From: Petter Strandmark Date: Sun, 28 Oct 2018 21:37:10 +0100 Subject: [PATCH 078/388] bpo-35047, unittest.mock: Better error messages on assert_called_xxx failures (GH-10090) unittest.mock now includes mock calls in exception messages if assert_not_called, assert_called_once, or assert_called_once_with fails. Backports: 47d94241a383e2b8a2c40e81d12d40d5947fb170 Signed-off-by: Chris Withers --- .../2018-10-25-09-59-00.bpo-35047.abbaa.rst | 3 ++ mock/mock.py | 36 ++++++++++++++----- mock/tests/testmock.py | 30 ++++++++++++++++ 3 files changed, 61 insertions(+), 8 deletions(-) create mode 100644 NEWS.d/2018-10-25-09-59-00.bpo-35047.abbaa.rst diff --git a/NEWS.d/2018-10-25-09-59-00.bpo-35047.abbaa.rst b/NEWS.d/2018-10-25-09-59-00.bpo-35047.abbaa.rst new file mode 100644 index 00000000..12eda275 --- /dev/null +++ b/NEWS.d/2018-10-25-09-59-00.bpo-35047.abbaa.rst @@ -0,0 +1,3 @@ +``unittest.mock`` now includes mock calls in exception messages if +``assert_not_called``, ``assert_called_once``, or ``assert_called_once_with`` +fails. Patch by Petter Strandmark. \ No newline at end of file diff --git a/mock/mock.py b/mock/mock.py index 3e4f6ea1..89b7bc43 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -63,6 +63,7 @@ except ImportError: import __builtin__ as builtins from types import ModuleType +from unittest.util import safe_repr import six from six import wraps @@ -913,8 +914,10 @@ def assert_not_called(_mock_self): """ self = _mock_self if self.call_count != 0: - msg = ("Expected '%s' to not have been called. Called %s times." % - (self._mock_name or 'mock', self.call_count)) + msg = ("Expected '%s' to not have been called. Called %s times.%s" + % (self._mock_name or 'mock', + self.call_count, + self._calls_repr())) raise AssertionError(msg) def assert_called(_mock_self): @@ -931,8 +934,10 @@ def assert_called_once(_mock_self): """ self = _mock_self if not self.call_count == 1: - msg = ("Expected '%s' to have been called once. Called %s times." % - (self._mock_name or 'mock', self.call_count)) + msg = ("Expected '%s' to have been called once. Called %s times.%s" + % (self._mock_name or 'mock', + self.call_count, + self._calls_repr())) raise AssertionError(msg) def assert_called_with(_mock_self, *args, **kwargs): @@ -963,8 +968,10 @@ def assert_called_once_with(_mock_self, *args, **kwargs): with the specified arguments.""" self = _mock_self if not self.call_count == 1: - msg = ("Expected '%s' to be called once. Called %s times." % - (self._mock_name or 'mock', self.call_count)) + msg = ("Expected '%s' to be called once. Called %s times.%s" + % (self._mock_name or 'mock', + self.call_count, + self._calls_repr())) raise AssertionError(msg) return self.assert_called_with(*args, **kwargs) @@ -985,8 +992,8 @@ def assert_has_calls(self, calls, any_order=False): if not any_order: if expected not in all_calls: six.raise_from(AssertionError( - 'Calls not found.\nExpected: %r\n' - 'Actual: %r' % (_CallList(calls), self.mock_calls) + 'Calls not found.\nExpected: %r%s' + % (_CallList(calls), self._calls_repr(prefix="Actual")) ), cause) return @@ -1047,6 +1054,19 @@ def _get_child_mock(self, **kw): return klass(**kw) + def _calls_repr(self, prefix="Calls"): + """Renders self.mock_calls as a string. + + Example: "\nCalls: [call(1), call(2)]." + + If self.mock_calls is empty, an empty string is returned. The + output will be truncated if very long. + """ + if not self.mock_calls: + return "" + return f"\n{prefix}: {safe_repr(self.mock_calls)}." + + def _try_iter(obj): if obj is None: diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 2f2c4c23..9b4ee7ee 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -4,6 +4,7 @@ import copy import pickle +import re import sys import tempfile @@ -432,6 +433,14 @@ def test_assert_called_once_with(self): lambda: mock.assert_called_once_with('bob', 'bar', baz=2) ) + def test_assert_called_once_with_call_list(self): + m = Mock() + m(1) + m(2) + self.assertRaisesRegex(AssertionError, + re.escape("Calls: [call(1), call(2)]"), + lambda: m.assert_called_once_with(2)) + def test_assert_called_once_with_function_spec(self): def f(a, b, c, d=None): @@ -1314,6 +1323,13 @@ def test_assert_not_called(self): with self.assertRaises(AssertionError): m.hello.assert_not_called() + def test_assert_not_called_message(self): + m = Mock() + m(1, 2) + self.assertRaisesRegex(AssertionError, + re.escape("Calls: [call(1, 2)]"), + m.assert_not_called) + def test_assert_called(self): m = Mock() with self.assertRaises(AssertionError): @@ -1335,6 +1351,20 @@ def test_assert_called_once(self): with self.assertRaises(AssertionError): m.hello.assert_called_once() + def test_assert_called_once_message(self): + m = Mock() + m(1, 2) + m(3) + self.assertRaisesRegex(AssertionError, + re.escape("Calls: [call(1, 2), call(3)]"), + m.assert_called_once) + + def test_assert_called_once_message_not_called(self): + m = Mock() + with self.assertRaises(AssertionError) as e: + 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): m = Mock() From e684d678a1437d5c0950ff6c862ef7a2495df70c Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 28 Apr 2019 20:48:13 +0100 Subject: [PATCH 079/388] backport note that f-strings should be re-written. --- docs/index.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/index.txt b/docs/index.txt index efa128a3..5ed2d8b4 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -142,6 +142,8 @@ 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. + Backporting process ------------------- From 5522ca0bac111cd714a538531756c3ad2b8d9a8a Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 28 Apr 2019 20:48:25 +0100 Subject: [PATCH 080/388] rewrite f-string. --- mock/mock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock/mock.py b/mock/mock.py index 89b7bc43..1bf9a5ce 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1064,7 +1064,7 @@ def _calls_repr(self, prefix="Calls"): """ if not self.mock_calls: return "" - return f"\n{prefix}: {safe_repr(self.mock_calls)}." + return "\n"+prefix+": "+safe_repr(self.mock_calls)+"." From 79e5a2671a56560fa5ed4325b27540fa6d3723c3 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 28 Apr 2019 20:49:12 +0100 Subject: [PATCH 081/388] latest sync point --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index 7888ac4b..a9b2873a 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -96200eb2ffcda05de14099cf23f60d5091366e3e +6c83d9f4a72905d968418bef670bb3091d2744db From d6563802b36255c74f6933f3cbb51c5138f1dde6 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 28 Apr 2019 22:38:05 +0100 Subject: [PATCH 082/388] python-2 spelling is assertRaisesRegexp --- docs/index.txt | 2 ++ mock/tests/testmock.py | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/index.txt b/docs/index.txt index 5ed2d8b4..3a321edd 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -144,6 +144,8 @@ Backporting rules - f-strings need to be rewritten using some other string substitution. +- ``assertRaisesRegex`` needs to be ``assertRaisesRegexp`` for Python 2. + Backporting process ------------------- diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 9b4ee7ee..a61dff1a 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -437,7 +437,7 @@ def test_assert_called_once_with_call_list(self): m = Mock() m(1) m(2) - self.assertRaisesRegex(AssertionError, + self.assertRaisesRegexp(AssertionError, re.escape("Calls: [call(1), call(2)]"), lambda: m.assert_called_once_with(2)) @@ -1326,7 +1326,7 @@ def test_assert_not_called(self): def test_assert_not_called_message(self): m = Mock() m(1, 2) - self.assertRaisesRegex(AssertionError, + self.assertRaisesRegexp(AssertionError, re.escape("Calls: [call(1, 2)]"), m.assert_not_called) @@ -1355,7 +1355,7 @@ def test_assert_called_once_message(self): m = Mock() m(1, 2) m(3) - self.assertRaisesRegex(AssertionError, + self.assertRaisesRegexp(AssertionError, re.escape("Calls: [call(1, 2), call(3)]"), m.assert_called_once) From a137eb09df64cbc5db24812b400d10dd18c55005 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 28 Apr 2019 22:38:16 +0100 Subject: [PATCH 083/388] mention --skip-reason in backporting docs. --- docs/index.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.txt b/docs/index.txt index 3a321edd..f4078677 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -183,7 +183,7 @@ Backporting process to get the patch committed. If it turns out that there's nothing that should be applied from the failed commit, - run ``python backport.py --skip-current``. + run ``python backport.py --skip-current``, maybe with ``--skip-reason``. If you have to make changes, please do a ``git commit --amend`` and add notes about what needed doing below the ``Signed-off-by`` block. From 2df18d018d8ecc84d4064f477162df0d9d3f4bcb Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 5 Nov 2018 16:20:25 +0200 Subject: [PATCH 084/388] bpo-35133: Fix mistakes when concatenate string literals on different lines. (GH-10284) Two kind of mistakes: 1. Missed space. After concatenating there is no space between words. 2. Missed comma. Causes unintentional concatenating in a list of strings. Backports: 34fd4c20198dea6ab2fe8dc6d32d744d9bde868d Signed-off-by: Chris Withers --- mock/mock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock/mock.py b/mock/mock.py index 1bf9a5ce..cb0a5771 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1921,7 +1921,7 @@ def method(self, *args, **kw): _unsupported_magics = { '__getattr__', '__setattr__', - '__init__', '__new__', '__prepare__' + '__init__', '__new__', '__prepare__', '__instancecheck__', '__subclasscheck__', '__del__' } From 5a593292f8e7e27da990e9c548b1e9fcd5c81bf1 Mon Sep 17 00:00:00 2001 From: Xtreak Date: Sat, 1 Dec 2018 15:33:54 +0530 Subject: [PATCH 085/388] bpo-31177: Skip deleted attributes while calling reset_mock (GH-9302) Backports: edeca92c84a3b08902ecdfe987cde00c7e617887 Signed-off-by: Chris Withers --- NEWS.d/2018-09-14-10-38-18.bpo-31177.Sv91TN.rst | 2 ++ mock/mock.py | 2 +- mock/tests/testmock.py | 10 ++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 NEWS.d/2018-09-14-10-38-18.bpo-31177.Sv91TN.rst diff --git a/NEWS.d/2018-09-14-10-38-18.bpo-31177.Sv91TN.rst b/NEWS.d/2018-09-14-10-38-18.bpo-31177.Sv91TN.rst new file mode 100644 index 00000000..f385571e --- /dev/null +++ b/NEWS.d/2018-09-14-10-38-18.bpo-31177.Sv91TN.rst @@ -0,0 +1,2 @@ +Fix bug that prevented using :meth:`reset_mock ` +on mock instances with deleted attributes diff --git a/mock/mock.py b/mock/mock.py index cb0a5771..a9b52ab6 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -673,7 +673,7 @@ def reset_mock(self, visited=None, return_value=False, side_effect=False): self._mock_side_effect = None for child in self._mock_children.values(): - if isinstance(child, _SpecState): + if isinstance(child, _SpecState) or child is _deleted: continue child.reset_mock(visited) diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index a61dff1a..cda41249 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -1662,6 +1662,16 @@ def test_attribute_deletion(self): self.assertRaises(AttributeError, getattr, mock, 'f') + def test_reset_mock_does_not_raise_on_attr_deletion(self): + # bpo-31177: reset_mock should not raise AttributeError when attributes + # were deleted in a mock instance + mock = Mock() + mock.child = True + del mock.child + mock.reset_mock() + self.assertFalse(hasattr(mock, 'child')) + + def test_class_assignable(self): for mock in Mock(), MagicMock(): self.assertNotIsInstance(mock, int) From 98f4fc477ea89472ac9fb75ed0b005a681c79304 Mon Sep 17 00:00:00 2001 From: Xtreak Date: Mon, 3 Dec 2018 13:28:15 +0530 Subject: [PATCH 086/388] bpo-32153: Add unit test for create_autospec with partial function returned in getattr (#10398) * Add create_autospec with partial function returned in getattr * Use self.assertFalse instead of assert * Use different names and remove object Backports: c667b094ae37799a7e42ba5cd2ad501cc7920888 Signed-off-by: Chris Withers --- mock/tests/testhelpers.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index e7c42f69..cc3e4f80 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -15,6 +15,7 @@ from mock.mock import _Call, _CallList from datetime import datetime +from functools import partial class SomeClass(object): def one(self, a, b): @@ -962,6 +963,17 @@ def test_autospec_socket(self): 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: + def __getattr__(self, attribute): + return partial(lambda name: name, attribute) + proxy = Foo() + autospec = create_autospec(proxy) + self.assertFalse(hasattr(autospec, '__name__')) + + class TestCallList(unittest.TestCase): def test_args_list_contains_call_list(self): From 52b1ecb000383c7cb413a12d997d15cc42904940 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 28 Apr 2019 22:44:11 +0100 Subject: [PATCH 087/388] latest sync point --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index a9b2873a..06bfdcc2 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -6c83d9f4a72905d968418bef670bb3091d2744db +edeca92c84a3b08902ecdfe987cde00c7e617887 From b496c468a11650008adc93509967abd66482aefb Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 28 Apr 2019 23:08:22 +0100 Subject: [PATCH 088/388] handle new news location --- NEWS.d/{next/Tests => }/2018-07-10-18-53-46.bpo-0.UBQJBc.rst | 0 backport.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename NEWS.d/{next/Tests => }/2018-07-10-18-53-46.bpo-0.UBQJBc.rst (100%) diff --git a/NEWS.d/next/Tests/2018-07-10-18-53-46.bpo-0.UBQJBc.rst b/NEWS.d/2018-07-10-18-53-46.bpo-0.UBQJBc.rst similarity index 100% rename from NEWS.d/next/Tests/2018-07-10-18-53-46.bpo-0.UBQJBc.rst rename to NEWS.d/2018-07-10-18-53-46.bpo-0.UBQJBc.rst diff --git a/backport.py b/backport.py index d2a276f0..671a44a1 100644 --- a/backport.py +++ b/backport.py @@ -59,7 +59,7 @@ def munge(rev, patch): ('(a|b)/Lib/unittest/mock.py', r'\1/mock/mock.py'), (r'(a|b)/Lib/unittest/test/testmock/(\S+)', r'\1/mock/tests/\2'), ('(a|b)/Misc/NEWS', r'\1/NEWS'), - ('(a|b)/NEWS.d/next/Library/(.+\.rst)', r'\1/NEWS.d/\2'), + ('(a|b)/NEWS.d/next/(Library|Tests)/(.+\.rst)', r'\1/NEWS.d/\3'), ): patch = re.sub(pattern, sub, patch) return patch From c95a767e2c8aca58a72cf627ce3637d5b838cf7f Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 29 Apr 2019 07:12:02 +0100 Subject: [PATCH 089/388] This needs to be a new-style class on Py2. --- mock/tests/testhelpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index cc3e4f80..863294e4 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -966,7 +966,7 @@ def test_autospec_socket(self): def test_autospec_getattr_partial_function(self): # bpo-32153 : getattr returning partial functions without # __name__ should not create AttributeError in create_autospec - class Foo: + class Foo(object): def __getattr__(self, attribute): return partial(lambda name: name, attribute) proxy = Foo() From c052ed132803a7ba4aa0067f9be3228a68ae95a7 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 3 Dec 2018 21:31:37 +0000 Subject: [PATCH 090/388] bpo-35226: Fix equality for nested unittest.mock.call objects. (#10555) Also refactor the call recording imolementation and add some notes about its limitations. Backports: 8ca0fa9d2f4de6e69f0902790432e0ab2f37ba68 Signed-off-by: Chris Withers --- .../2018-11-15-07-14-32.bpo-35226.wJPEEe.rst | 3 + mock/mock.py | 55 +++++++++++-------- mock/tests/testhelpers.py | 16 ++++++ mock/tests/testmock.py | 51 +++++++++++++++++ 4 files changed, 102 insertions(+), 23 deletions(-) create mode 100644 NEWS.d/2018-11-15-07-14-32.bpo-35226.wJPEEe.rst diff --git a/NEWS.d/2018-11-15-07-14-32.bpo-35226.wJPEEe.rst b/NEWS.d/2018-11-15-07-14-32.bpo-35226.wJPEEe.rst new file mode 100644 index 00000000..b95cc979 --- /dev/null +++ b/NEWS.d/2018-11-15-07-14-32.bpo-35226.wJPEEe.rst @@ -0,0 +1,3 @@ +Recursively check arguments when testing for equality of +:class:`unittest.mock.call` objects and add note that tracking of parameters +used to create ancestors of mocks in ``mock_calls`` is not possible. diff --git a/mock/mock.py b/mock/mock.py index a9b52ab6..88f30b5c 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1115,46 +1115,51 @@ def _mock_call(_mock_self, *args, **kwargs): self = _mock_self self.called = True self.call_count += 1 - _new_name = self._mock_new_name - _new_parent = self._mock_new_parent + # handle call_args _call = _Call((args, kwargs), two=True) self.call_args = _call self.call_args_list.append(_call) - self.mock_calls.append(_Call(('', args, kwargs))) seen = set() - skip_next_dot = _new_name == '()' + + # initial stuff for method_calls: do_method_calls = self._mock_parent is not None - name = self._mock_name - while _new_parent is not None: - this_mock_call = _Call((_new_name, args, kwargs)) - if _new_parent._mock_new_name: - dot = '.' - if skip_next_dot: - dot = '' + method_call_name = self._mock_name - skip_next_dot = False - if _new_parent._mock_new_name == '()': - skip_next_dot = True + # initial stuff for mock_calls: + mock_call_name = self._mock_new_name + is_a_call = mock_call_name == '()' + self.mock_calls.append(_Call(('', args, kwargs))) - _new_name = _new_parent._mock_new_name + dot + _new_name + # follow up the chain of mocks: + _new_parent = self._mock_new_parent + while _new_parent is not None: + # handle method_calls: if do_method_calls: - if _new_name == name: - this_method_call = this_mock_call - else: - this_method_call = _Call((name, args, kwargs)) - _new_parent.method_calls.append(this_method_call) - + _new_parent.method_calls.append(_Call((method_call_name, args, kwargs))) do_method_calls = _new_parent._mock_parent is not None if do_method_calls: - name = _new_parent._mock_name + '.' + name + method_call_name = _new_parent._mock_name + '.' + method_call_name + # handle mock_calls: + this_mock_call = _Call((mock_call_name, args, kwargs)) _new_parent.mock_calls.append(this_mock_call) + + if _new_parent._mock_new_name: + if is_a_call: + dot = '' + else: + dot = '.' + is_a_call = _new_parent._mock_new_name == '()' + mock_call_name = _new_parent._mock_new_name + dot + mock_call_name + + # follow the parental chain: _new_parent = _new_parent._mock_new_parent - # use ids here so as not to call __hash__ on the mocks + # check we're not in an infinite loop: + # ( use ids here so as not to call __hash__ on the mocks) _new_parent_id = id(_new_parent) if _new_parent_id in seen: break @@ -2217,6 +2222,10 @@ def __eq__(self, other): else: self_name, self_args, self_kwargs = self + if (getattr(self, 'parent', None) and getattr(other, 'parent', None) + and self.parent != other.parent): + return False + other_name = '' if len_other == 0: other_args, other_kwargs = (), {} diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index 863294e4..a0c61bfc 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -277,6 +277,22 @@ def test_extended_call(self): self.assertEqual(mock.mock_calls, last_call.call_list()) + def test_extended_not_equal(self): + a = call(x=1).foo + b = call(x=2).foo + self.assertEqual(a, a) + self.assertEqual(b, b) + self.assertNotEqual(a, b) + + + def test_nested_calls_not_equal(self): + a = call(x=1).foo().bar + b = call(x=2).foo().bar + self.assertEqual(a, a) + self.assertEqual(b, b) + self.assertNotEqual(a, b) + + def test_call_list(self): mock = MagicMock() mock(1) diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index cda41249..53dda6f7 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -987,6 +987,57 @@ def test_mock_calls(self): call().__int__().call_list()) + def test_child_mock_call_equal(self): + m = Mock() + result = m() + result.wibble() + # parent looks like this: + self.assertEqual(m.mock_calls, [call(), call().wibble()]) + # but child should look like this: + self.assertEqual(result.mock_calls, [call.wibble()]) + + + def test_mock_call_not_equal_leaf(self): + m = Mock() + m.foo().something() + self.assertNotEqual(m.mock_calls[1], call.foo().different()) + self.assertEqual(m.mock_calls[0], call.foo()) + + + def test_mock_call_not_equal_non_leaf(self): + m = Mock() + m.foo().bar() + self.assertNotEqual(m.mock_calls[1], call.baz().bar()) + self.assertNotEqual(m.mock_calls[0], call.baz()) + + + def test_mock_call_not_equal_non_leaf_params_different(self): + m = Mock() + m.foo(x=1).bar() + # This isn't ideal, but there's no way to fix it without breaking backwards compatibility: + self.assertEqual(m.mock_calls[1], call.foo(x=2).bar()) + + + def test_mock_call_not_equal_non_leaf_attr(self): + m = Mock() + m.foo.bar() + self.assertNotEqual(m.mock_calls[0], call.baz.bar()) + + + def test_mock_call_not_equal_non_leaf_call_versus_attr(self): + m = Mock() + m.foo.bar() + self.assertNotEqual(m.mock_calls[0], call.foo().bar()) + + + def test_mock_call_repr(self): + m = Mock() + m.foo().bar().baz.bob() + self.assertEqual(repr(m.mock_calls[0]), 'call.foo()') + self.assertEqual(repr(m.mock_calls[1]), 'call.foo().bar()') + self.assertEqual(repr(m.mock_calls[2]), 'call.foo().bar().baz.bob()') + + def test_subclassing(self): class Subclass(Mock): pass From 962077b08d62752a3655b69f46c72872aefc1833 Mon Sep 17 00:00:00 2001 From: Andrew Dunai Date: Tue, 4 Dec 2018 11:08:45 +0200 Subject: [PATCH 091/388] bpo-35357: Add _mock_ prefix to name/parent/from_kall attributes of _Call/_MagicProxy. (#10873) Fix minor typo in test function name. Backports: e63e617ebbe481c498bdf037a62e09f4f9f3963f Signed-off-by: Chris Withers --- .../2018-12-03-21-20-24.bpo-35357.rhhoiC.rst | 4 +++ mock/mock.py | 28 +++++++++---------- mock/tests/testmock.py | 18 ++++++++++-- 3 files changed, 33 insertions(+), 17 deletions(-) create mode 100644 NEWS.d/2018-12-03-21-20-24.bpo-35357.rhhoiC.rst diff --git a/NEWS.d/2018-12-03-21-20-24.bpo-35357.rhhoiC.rst b/NEWS.d/2018-12-03-21-20-24.bpo-35357.rhhoiC.rst new file mode 100644 index 00000000..1dade5ba --- /dev/null +++ b/NEWS.d/2018-12-03-21-20-24.bpo-35357.rhhoiC.rst @@ -0,0 +1,4 @@ +Internal attributes' names of unittest.mock._Call and +unittest.mock.MagicProxy (name, parent & from_kall) are now prefixed with +_mock_ in order to prevent clashes with widely used object attributes. +Fixed minor typo in test function name. diff --git a/mock/mock.py b/mock/mock.py index 88f30b5c..9160268b 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2203,9 +2203,9 @@ def __new__(cls, value=(), name='', parent=None, two=False, def __init__(self, value=(), name=None, parent=None, two=False, from_kall=True): - self.name = name - self.parent = parent - self.from_kall = from_kall + self._mock_name = name + self._mock_parent = parent + self._mock_from_kall = from_kall def __eq__(self, other): @@ -2222,8 +2222,8 @@ def __eq__(self, other): else: self_name, self_args, self_kwargs = self - if (getattr(self, 'parent', None) and getattr(other, 'parent', None) - and self.parent != other.parent): + if (getattr(self, '_mock_parent', None) and getattr(other, '_mock_parent', None) + and self._mock_parent != other._mock_parent): return False other_name = '' @@ -2269,17 +2269,17 @@ def __ne__(self, other): __hash__ = None def __call__(self, *args, **kwargs): - if self.name is None: + if self._mock_name is None: return _Call(('', args, kwargs), name='()') - name = self.name + '()' - return _Call((self.name, args, kwargs), name=name, parent=self) + name = self._mock_name + '()' + return _Call((self._mock_name, args, kwargs), name=name, parent=self) def __getattr__(self, attr): - if self.name is None: + if self._mock_name is None: return _Call(name=attr, from_kall=False) - name = '{}.{}'.format(self.name, attr) + name = '{}.{}'.format(self._mock_name, attr) return _Call(name=name, parent=self, from_kall=False) @@ -2290,8 +2290,8 @@ def index(self, *args, **kwargs): return self.__getattr__('index')(*args, **kwargs) def __repr__(self): - if not self.from_kall: - name = self.name or 'call' + if not self._mock_from_kall: + name = self._mock_name or 'call' if name.startswith('()'): name = 'call%s' % name return name @@ -2317,9 +2317,9 @@ def call_list(self): vals = [] thing = self while thing is not None: - if thing.from_kall: + if thing._mock_from_kall: vals.append(thing) - thing = thing.parent + thing = thing._mock_parent return _CallList(reversed(vals)) diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 53dda6f7..43e23b26 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -12,13 +12,12 @@ import unittest import mock -from mock import ( +from mock.mock import ( call, DEFAULT, patch, sentinel, MagicMock, Mock, NonCallableMock, - NonCallableMagicMock, + NonCallableMagicMock, _Call, _CallList, create_autospec ) -from mock.mock import _CallList from mock.tests.support import is_instance @@ -1731,6 +1730,19 @@ def test_class_assignable(self): self.assertIsInstance(mock, int) mock.foo + def test_name_attribute_of_call(self): + # bpo-35357: _Call should not disclose any attributes whose names + # may clash with popular ones (such as ".name") + self.assertIsNotNone(call.name) + self.assertEqual(type(call.name), _Call) + self.assertEqual(type(call.name().name), _Call) + + def test_parent_attribute_of_call(self): + # bpo-35357: _Call should not disclose any attributes whose names + # may clash with popular ones (such as ".parent") + self.assertIsNotNone(call.parent) + self.assertEqual(type(call.parent), _Call) + self.assertEqual(type(call.parent().parent), _Call) @unittest.expectedFailure def test_pickle(self): From 7c85fb2f679003fc676a3c05ffc27b2022b72883 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 29 Apr 2019 07:21:04 +0100 Subject: [PATCH 092/388] handle more news locations --- backport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backport.py b/backport.py index 671a44a1..95e136fd 100644 --- a/backport.py +++ b/backport.py @@ -59,7 +59,7 @@ def munge(rev, patch): ('(a|b)/Lib/unittest/mock.py', r'\1/mock/mock.py'), (r'(a|b)/Lib/unittest/test/testmock/(\S+)', r'\1/mock/tests/\2'), ('(a|b)/Misc/NEWS', r'\1/NEWS'), - ('(a|b)/NEWS.d/next/(Library|Tests)/(.+\.rst)', r'\1/NEWS.d/\3'), + ('(a|b)/NEWS.d/next/[^/]+/(.+\.rst)', r'\1/NEWS.d/\2'), ): patch = re.sub(pattern, sub, patch) return patch From e95565ab48355e4e1c707ce4b1b141ec9dda9787 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 29 Apr 2019 07:22:00 +0100 Subject: [PATCH 093/388] Backports: 20428527a7c188d988d20b267cfef58da10b0fc9, skipped: it has no changes needed here. --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index 06bfdcc2..840c0ec9 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -edeca92c84a3b08902ecdfe987cde00c7e617887 +20428527a7c188d988d20b267cfef58da10b0fc9 From 2a0b4250a133cb5aabcfb859e1de216747a220e3 Mon Sep 17 00:00:00 2001 From: Anirudha Bose Date: Sat, 8 Dec 2018 00:30:42 +0100 Subject: [PATCH 094/388] bpo-33747: Avoid mutating the global sys.modules dict in unittest.mock tests (GH-8520) Backports: 3cf74384b53b998fa846dc2590cedf9ad2a0d5fd Signed-off-by: Chris Withers --- mock/tests/testpatch.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index 730fb486..d4aef875 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -1669,20 +1669,19 @@ def test_mock_calls_with_patch(self): def test_patch_imports_lazily(self): - sys.modules.pop('squizz', None) - p1 = patch('squizz.squozz') self.assertRaises(ImportError, p1.start) - squizz = Mock() - squizz.squozz = 6 - sys.modules['squizz'] = squizz - p1 = patch('squizz.squozz') - squizz.squozz = 3 - p1.start() - p1.stop() - self.assertEqual(squizz.squozz, 3) + with uncache('squizz'): + squizz = Mock() + sys.modules['squizz'] = squizz + squizz.squozz = 6 + p1 = patch('squizz.squozz') + squizz.squozz = 3 + p1.start() + p1.stop() + self.assertEqual(squizz.squozz, 3) def test_patch_propogrates_exc_on_exit(self): class holder: @@ -1705,7 +1704,12 @@ def with_custom_patch(target): def test(mock): raise RuntimeError - self.assertRaises(RuntimeError, test) + with uncache('squizz'): + squizz = Mock() + sys.modules['squizz'] = squizz + + self.assertRaises(RuntimeError, test) + self.assertIs(holder.exc_info[0], RuntimeError) self.assertIsNotNone(holder.exc_info[1], 'exception value not propgated') From b1c060e558639a95f98b13244367da9f96810aa9 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 29 Apr 2019 07:27:07 +0100 Subject: [PATCH 095/388] bring uncache helper across from cpython --- mock/tests/support.py | 30 ++++++++++++++++++++++++++++++ mock/tests/testpatch.py | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/mock/tests/support.py b/mock/tests/support.py index c7ad20b8..2bbc8a24 100644 --- a/mock/tests/support.py +++ b/mock/tests/support.py @@ -1,3 +1,7 @@ +import contextlib +import sys + + def is_instance(obj, klass): """Version of is_instance that doesn't access __class__""" return issubclass(type(obj), klass) @@ -12,3 +16,29 @@ def wibble(self): 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 d4aef875..434e780e 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -9,7 +9,7 @@ import unittest from mock.tests import support -from mock.tests.support import SomeClass, is_instance +from mock.tests.support import SomeClass, is_instance, uncache from mock import ( NonCallableMock, CallableMixin, patch, sentinel, From 796edd45b3eab4dc8b9fe17c528bc51f6a613b67 Mon Sep 17 00:00:00 2001 From: Mario Corchero Date: Sat, 8 Dec 2018 11:25:02 +0000 Subject: [PATCH 096/388] bpo-35330: Don't call the wrapped object if `side_effect` is set (GH10973) * tests: Further validate `wraps` functionality in `unittest.mock.Mock` Add more tests to validate how `wraps` interacts with other features of mocks. * Don't call the wrapped object if `side_effect` is set When a object is wrapped using `Mock(wraps=...)`, if an user sets a `side_effect` in one of their methods, return the value of `side_effect` and don't call the original object. * Refactor what to be called on `mock_call` When a `Mock` is called, it should return looking up in the following order: `side_effect`, `return_value`, `wraps`. If any of the first two return `mock.DEFAULT`, lookup in the next option. It makes no sense to check for `wraps` returning default, as it is supposed to be the original implementation and there is nothing to fallback to. Backports: f05df0a4b679d0acfd0b1fe6187ba2d553b37afa Signed-off-by: Chris Withers --- .../2018-12-06-00-43-13.bpo-35330.abB4BN.rst | 4 + mock/mock.py | 21 ++- mock/tests/testmock.py | 122 ++++++++++++++++++ 3 files changed, 136 insertions(+), 11 deletions(-) create mode 100644 NEWS.d/2018-12-06-00-43-13.bpo-35330.abB4BN.rst diff --git a/NEWS.d/2018-12-06-00-43-13.bpo-35330.abB4BN.rst b/NEWS.d/2018-12-06-00-43-13.bpo-35330.abB4BN.rst new file mode 100644 index 00000000..24d0ab84 --- /dev/null +++ b/NEWS.d/2018-12-06-00-43-13.bpo-35330.abB4BN.rst @@ -0,0 +1,4 @@ +When a :class:`Mock` instance was used to wrap an object, if `side_effect` +is used in one of the mocks of it methods, don't call the original +implementation and return the result of using the side effect the same way +that it is done with return_value. diff --git a/mock/mock.py b/mock/mock.py index 9160268b..2724fae4 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1165,28 +1165,27 @@ def _mock_call(_mock_self, *args, **kwargs): break seen.add(_new_parent_id) - ret_val = DEFAULT effect = self.side_effect if effect is not None: if _is_exception(effect): raise effect - - if not _callable(effect): + elif not _callable(effect): result = next(effect) if _is_exception(result): raise result - if result is DEFAULT: - result = self.return_value + else: + result = effect(*args, **kwargs) + + if result is not DEFAULT: return result - ret_val = effect(*args, **kwargs) + if self._mock_return_value is not DEFAULT: + return self.return_value - if (self._mock_wraps is not None and - self._mock_return_value is DEFAULT): + if self._mock_wraps is not None: return self._mock_wraps(*args, **kwargs) - if ret_val is DEFAULT: - ret_val = self.return_value - return ret_val + + return self.return_value diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 43e23b26..8a8e7c31 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -583,6 +583,16 @@ def test_wraps_calls(self): real.assert_called_with(1, 2, fish=3) + def test_wraps_prevents_automatic_creation_of_mocks(self): + class Real(object): + pass + + real = Real() + mock = Mock(wraps=real) + + self.assertRaises(AttributeError, lambda: mock.new_attr()) + + def test_wraps_call_with_nondefault_return_value(self): real = Mock() @@ -609,6 +619,118 @@ class Real(object): self.assertEqual(result, Real.attribute.frog()) + def test_customize_wrapped_object_with_side_effect_iterable_with_default(self): + class Real(object): + def method(self): + return sentinel.ORIGINAL_VALUE + + real = Real() + mock = Mock(wraps=real) + mock.method.side_effect = [sentinel.VALUE1, DEFAULT] + + self.assertEqual(mock.method(), sentinel.VALUE1) + self.assertEqual(mock.method(), sentinel.ORIGINAL_VALUE) + self.assertRaises(StopIteration, mock.method) + + + def test_customize_wrapped_object_with_side_effect_iterable(self): + class Real(object): + def method(self): + raise NotImplementedError() + + real = Real() + mock = Mock(wraps=real) + mock.method.side_effect = [sentinel.VALUE1, sentinel.VALUE2] + + self.assertEqual(mock.method(), sentinel.VALUE1) + self.assertEqual(mock.method(), sentinel.VALUE2) + self.assertRaises(StopIteration, mock.method) + + + def test_customize_wrapped_object_with_side_effect_exception(self): + class Real(object): + def method(self): + raise NotImplementedError() + + real = Real() + mock = Mock(wraps=real) + mock.method.side_effect = RuntimeError + + self.assertRaises(RuntimeError, mock.method) + + + def test_customize_wrapped_object_with_side_effect_function(self): + class Real(object): + def method(self): + raise NotImplementedError() + + def side_effect(): + return sentinel.VALUE + + real = Real() + mock = Mock(wraps=real) + mock.method.side_effect = side_effect + + self.assertEqual(mock.method(), sentinel.VALUE) + + + def test_customize_wrapped_object_with_return_value(self): + class Real(object): + def method(self): + raise NotImplementedError() + + real = Real() + mock = Mock(wraps=real) + mock.method.return_value = sentinel.VALUE + + self.assertEqual(mock.method(), sentinel.VALUE) + + + def test_customize_wrapped_object_with_return_value_and_side_effect(self): + # side_effect should always take precedence over return_value. + class Real(object): + def method(self): + raise NotImplementedError() + + real = Real() + mock = Mock(wraps=real) + mock.method.side_effect = [sentinel.VALUE1, sentinel.VALUE2] + mock.method.return_value = sentinel.WRONG_VALUE + + self.assertEqual(mock.method(), sentinel.VALUE1) + self.assertEqual(mock.method(), sentinel.VALUE2) + self.assertRaises(StopIteration, mock.method) + + + def test_customize_wrapped_object_with_return_value_and_side_effect2(self): + # side_effect can return DEFAULT to default to return_value + class Real(object): + def method(self): + raise NotImplementedError() + + real = Real() + mock = Mock(wraps=real) + mock.method.side_effect = lambda: DEFAULT + mock.method.return_value = sentinel.VALUE + + self.assertEqual(mock.method(), sentinel.VALUE) + + + def test_customize_wrapped_object_with_return_value_and_side_effect_default(self): + class Real(object): + def method(self): + raise NotImplementedError() + + real = Real() + mock = Mock(wraps=real) + mock.method.side_effect = [sentinel.VALUE1, DEFAULT] + mock.method.return_value = sentinel.RETURN + + self.assertEqual(mock.method(), sentinel.VALUE1) + self.assertEqual(mock.method(), sentinel.RETURN) + self.assertRaises(StopIteration, mock.method) + + def test_exceptional_side_effect(self): mock = Mock(side_effect=AttributeError) self.assertRaises(AttributeError, mock) From a7b0ba10a004d3252b9f83684eb5aca1174efb07 Mon Sep 17 00:00:00 2001 From: Xtreak Date: Wed, 12 Dec 2018 13:24:54 +0530 Subject: [PATCH 097/388] bpo-17185: Add __signature__ to mock that can be used by inspect for signature (GH11048) * Fix partial and partial method signatures in mock * Add more calls * Add NEWS entry * Use assertEquals and fix markup in NEWS * Refactor branching and add markup reference for functools * Revert partial object related changes and fix pr comments Backports: f7fa62ef4422c9deee050a794fd8504640d9f8f4 Signed-off-by: Chris Withers --- .../2018-12-09-17-04-15.bpo-17185.SfSCJF.rst | 2 ++ mock/mock.py | 6 ++-- mock/tests/testhelpers.py | 30 +++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 NEWS.d/2018-12-09-17-04-15.bpo-17185.SfSCJF.rst diff --git a/NEWS.d/2018-12-09-17-04-15.bpo-17185.SfSCJF.rst b/NEWS.d/2018-12-09-17-04-15.bpo-17185.SfSCJF.rst new file mode 100644 index 00000000..311c6d2b --- /dev/null +++ b/NEWS.d/2018-12-09-17-04-15.bpo-17185.SfSCJF.rst @@ -0,0 +1,2 @@ +Set ``__signature__`` on mock for :mod:`inspect` to get signature. +Patch by Karthikeyan Singaravelan. diff --git a/mock/mock.py b/mock/mock.py index 2724fae4..0d12bc1b 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -206,6 +206,7 @@ def checksig(_mock_self, *args, **kwargs): sig.bind(*args, **kwargs) _copy_func_details(func, checksig) type(mock)._mock_check_sig = checksig + type(mock).__signature__ = sig def _copy_func_details(func, funcopy): @@ -290,11 +291,11 @@ def checksig(*args, **kwargs): return mock(*args, **kwargs)""" % name six.exec_(src, context) funcopy = context[name] - _setup_func(funcopy, mock) + _setup_func(funcopy, mock, sig) return funcopy -def _setup_func(funcopy, mock): +def _setup_func(funcopy, mock, sig): funcopy.mock = mock # can't use isinstance with mocks @@ -342,6 +343,7 @@ def reset_mock(): funcopy.assert_called = assert_called funcopy.assert_not_called = assert_not_called funcopy.assert_called_once = assert_called_once + funcopy.__signature__ = sig mock._mock_delegate = funcopy diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index a0c61bfc..2734f862 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -3,6 +3,7 @@ # http://www.voidspace.org.uk/python/mock/ import socket +import inspect import six import sys import time @@ -990,6 +991,35 @@ def __getattr__(self, attribute): self.assertFalse(hasattr(autospec, '__name__')) + def test_spec_inspect_signature(self): + + def myfunc(x, y): + pass + + mock = create_autospec(myfunc) + mock(1, 2) + mock(x=1, y=2) + + self.assertEqual(inspect.getfullargspec(mock), inspect.getfullargspec(myfunc)) + self.assertEqual(mock.mock_calls, [call(1, 2), call(x=1, y=2)]) + self.assertRaises(TypeError, mock, 1) + + + def test_spec_inspect_signature_annotations(self): + + def foo(a: int, b: int=10, *, c:int) -> int: + return a + b + c + + 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) + + class TestCallList(unittest.TestCase): def test_args_list_contains_call_list(self): From 99b1b189e539064859866de2f4ab2c9c7854c537 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 29 Apr 2019 08:03:57 +0100 Subject: [PATCH 098/388] Flip to pytest so we can exclude files that won't parse on Py2. --- .travis.yml | 2 +- pytest.ini | 4 ++++ setup.cfg | 2 ++ tox.ini | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 pytest.ini diff --git a/.travis.yml b/.travis.yml index ebcb0dbb..2764b966 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,6 @@ install: - pip list - python --version script: - - python -m unittest discover + - pytest - if [ -z "$SKIP_DOCS" ]; then python setup.py build_sphinx; fi - rst2html.py --strict README.rst README.html diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..af8b4498 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +python_files=test*.py +filterwarnings = + ignore::DeprecationWarning diff --git a/setup.cfg b/setup.cfg index f99b6041..5597e2fb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -32,6 +32,8 @@ keyword = [extras] docs = sphinx +test = + pytest [files] packages = mock diff --git a/tox.ini b/tox.ini index be80fed6..cdef1015 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,7 @@ envlist = py27,pypy,py34,py35,py36,py37jython,docs [testenv] commands = - {envbindir}/python -m unittest discover {posargs} + {envbindir}/pytest {posargs} [testenv:docs] deps = From a2e00f460953e2141684f4b6d7ad0097d37c340d Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 29 Apr 2019 08:17:45 +0100 Subject: [PATCH 099/388] move python 3-only code to its own file. --- docs/index.txt | 4 ++++ mock/tests/conftest.py | 6 ++++++ mock/tests/testhelpers.py | 15 --------------- mock/tests/testhelpers_py3.py | 22 ++++++++++++++++++++++ 4 files changed, 32 insertions(+), 15 deletions(-) create mode 100644 mock/tests/conftest.py create mode 100644 mock/tests/testhelpers_py3.py diff --git a/docs/index.txt b/docs/index.txt index f4078677..7f4dca20 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -146,6 +146,10 @@ Backporting rules - ``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``. + Backporting process ------------------- diff --git a/mock/tests/conftest.py b/mock/tests/conftest.py new file mode 100644 index 00000000..78831f6f --- /dev/null +++ b/mock/tests/conftest.py @@ -0,0 +1,6 @@ +import six + + +def pytest_ignore_collect(path): + if 'py3' in path.basename and six.PY2: + return True diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index 2734f862..a0762971 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -1005,21 +1005,6 @@ def myfunc(x, y): self.assertRaises(TypeError, mock, 1) - def test_spec_inspect_signature_annotations(self): - - def foo(a: int, b: int=10, *, c:int) -> int: - return a + b + c - - 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) - - class TestCallList(unittest.TestCase): def test_args_list_contains_call_list(self): diff --git a/mock/tests/testhelpers_py3.py b/mock/tests/testhelpers_py3.py new file mode 100644 index 00000000..2af91b50 --- /dev/null +++ b/mock/tests/testhelpers_py3.py @@ -0,0 +1,22 @@ +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 + + 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) From 6611a75ef0c464fb11bdb6f731aa4d0366536b55 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 29 Apr 2019 08:25:36 +0100 Subject: [PATCH 100/388] Have to use the funcsigs backport on Py2. --- mock/tests/testhelpers.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index a0762971..34a8eae7 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -18,6 +18,11 @@ from datetime import datetime from functools import partial + +if six.PY2: + import funcsigs + + class SomeClass(object): def one(self, a, b): pass @@ -1000,7 +1005,10 @@ def myfunc(x, y): mock(1, 2) mock(x=1, y=2) - self.assertEqual(inspect.getfullargspec(mock), inspect.getfullargspec(myfunc)) + if six.PY2: + self.assertEqual(funcsigs.signature(mock), funcsigs.signature(myfunc)) + else: + self.assertEqual(inspect.getfullargspec(mock), inspect.getfullargspec(myfunc)) self.assertEqual(mock.mock_calls, [call(1, 2), call(x=1, y=2)]) self.assertRaises(TypeError, mock, 1) From c86fadfdcdcb4a9157d6591f76e89d2f2a2f840f Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 11 Dec 2018 23:56:35 -0800 Subject: [PATCH 101/388] Add test for double patching instance methods (#11085) Backports: 5a718e918db6211b633a7afb2bf537eb5b56cb1b Signed-off-by: Chris Withers --- NEWS.d/2018-12-10-13-18-37.bpo-26704.DBAN4c.rst | 2 ++ mock/tests/testwith.py | 14 ++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 NEWS.d/2018-12-10-13-18-37.bpo-26704.DBAN4c.rst diff --git a/NEWS.d/2018-12-10-13-18-37.bpo-26704.DBAN4c.rst b/NEWS.d/2018-12-10-13-18-37.bpo-26704.DBAN4c.rst new file mode 100644 index 00000000..458f495b --- /dev/null +++ b/NEWS.d/2018-12-10-13-18-37.bpo-26704.DBAN4c.rst @@ -0,0 +1,2 @@ +Added test demonstrating double-patching of an instance method. Patch by +Anthony Sottile. diff --git a/mock/tests/testwith.py b/mock/tests/testwith.py index 0297d980..ce6e08c0 100644 --- a/mock/tests/testwith.py +++ b/mock/tests/testwith.py @@ -131,6 +131,20 @@ def test_dict_context_manager(self): self.assertEqual(foo, {}) + def test_double_patch_instance_method(self): + class C: + def f(self): + pass + + c = C() + + with patch.object(c, 'f', autospec=True) as patch1: + with patch.object(c, 'f', autospec=True) as patch2: + c.f() + self.assertEqual(patch2.call_count, 1) + self.assertEqual(patch1.call_count, 0) + c.f() + self.assertEqual(patch1.call_count, 1) class TestMockOpen(unittest.TestCase): From 2a31bd034c5077ba1a9d9bb98c516c84e0cb0f0a Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 30 Apr 2019 07:27:00 +0100 Subject: [PATCH 102/388] fix double patching on Py2. --- mock/mock.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mock/mock.py b/mock/mock.py index 0d12bc1b..6b691862 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2400,6 +2400,12 @@ 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 From 031ea7664e211d3d462de8b34deafada04ccf578 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 30 Apr 2019 07:27:06 +0100 Subject: [PATCH 103/388] These appear to be unsued. --- mock/mock.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/mock/mock.py b/mock/mock.py index 6b691862..d9a5df66 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -126,11 +126,6 @@ def _isidentifier(string): return False return regex.match(string) -self = 'im_self' -builtin = '__builtin__' -if six.PY3: - self = '__self__' - builtin = 'builtins' # NOTE: This FILTER_DIR is not used. The binding in mock.FILTER_DIR is. FILTER_DIR = True From 019b1b5fce34604b87a8046fa60fb400328f56f8 Mon Sep 17 00:00:00 2001 From: Xtreak Date: Thu, 20 Dec 2018 21:30:21 +0530 Subject: [PATCH 104/388] Fix mock_open docstring to use readline (#11176) Backports: 71f82a2f2085464f5ec99c16bce57bd1631733bd Signed-off-by: Chris Withers --- mock/mock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock/mock.py b/mock/mock.py index d9a5df66..787fa598 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2544,7 +2544,7 @@ def mock_open(mock=None, read_data=''): default) then a `MagicMock` will be created for you, with the API limited to methods or attributes available on standard file handles. - `read_data` is a string for the `read` methoddline`, and `readlines` of the + `read_data` is a string for the `read`, `readline` and `readlines` of the file handle to return. This is an empty string by default. """ def _readlines_side_effect(*args, **kwargs): From 373301ed9cc7c964a45cb37f86f6c3696c190eb7 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Mon, 21 Jan 2019 08:57:46 +0000 Subject: [PATCH 105/388] bpo-20239: Allow repeated deletion of unittest.mock.Mock attributes (#11057) * Allow repeated deletion of unittest.mock.Mock attributes * fixup! Allow repeated deletion of unittest.mock.Mock attributes * fixup! fixup! Allow repeated deletion of unittest.mock.Mock attributes Backports: 222d303ade8aadf0adcae5190fac603bdcafe3f0 Signed-off-by: Chris Withers --- .../2018-12-09-21-35-49.bpo-20239.V4mWBL.rst | 2 ++ mock/mock.py | 7 +++-- mock/tests/testmock.py | 27 +++++++++++++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 NEWS.d/2018-12-09-21-35-49.bpo-20239.V4mWBL.rst diff --git a/NEWS.d/2018-12-09-21-35-49.bpo-20239.V4mWBL.rst b/NEWS.d/2018-12-09-21-35-49.bpo-20239.V4mWBL.rst new file mode 100644 index 00000000..fe9c69d2 --- /dev/null +++ b/NEWS.d/2018-12-09-21-35-49.bpo-20239.V4mWBL.rst @@ -0,0 +1,2 @@ +Allow repeated assignment deletion of :class:`unittest.mock.Mock` attributes. +Patch by Pablo Galindo. diff --git a/mock/mock.py b/mock/mock.py index 787fa598..7324ec1e 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -858,11 +858,10 @@ def __delattr__(self, name): # not set on the instance itself return - if name in self.__dict__: - object.__delattr__(self, name) - obj = self._mock_children.get(name, _missing) - if obj is _deleted: + if name in self.__dict__: + super().__delattr__(name) + elif obj is _deleted: raise AttributeError(name) if obj is not _missing: del self._mock_children[name] diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 8a8e7c31..b496a361 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -1834,6 +1834,33 @@ def test_attribute_deletion(self): self.assertRaises(AttributeError, getattr, mock, 'f') + def test_mock_does_not_raise_on_repeated_attribute_deletion(self): + # bpo-20239: Assigning and deleting twice an attribute raises. + for mock in (Mock(), MagicMock(), NonCallableMagicMock(), + NonCallableMock()): + mock.foo = 3 + self.assertTrue(hasattr(mock, 'foo')) + self.assertEqual(mock.foo, 3) + + del mock.foo + self.assertFalse(hasattr(mock, 'foo')) + + mock.foo = 4 + self.assertTrue(hasattr(mock, 'foo')) + self.assertEqual(mock.foo, 4) + + del mock.foo + self.assertFalse(hasattr(mock, 'foo')) + + + def test_mock_raises_when_deleting_nonexistent_attribute(self): + for mock in (Mock(), MagicMock(), NonCallableMagicMock(), + NonCallableMock()): + del mock.foo + with self.assertRaises(AttributeError): + del mock.foo + + def test_reset_mock_does_not_raise_on_attr_deletion(self): # bpo-31177: reset_mock should not raise AttributeError when attributes # were deleted in a mock instance From dde37a2af22d6bae81d4c3736567c24ebed46fa7 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 30 Apr 2019 07:36:59 +0100 Subject: [PATCH 106/388] Py2-compatible super call --- mock/mock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock/mock.py b/mock/mock.py index 7324ec1e..bbf23147 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -860,7 +860,7 @@ def __delattr__(self, name): obj = self._mock_children.get(name, _missing) if name in self.__dict__: - super().__delattr__(name) + _safe_super(NonCallableMock, self).__delattr__(name) elif obj is _deleted: raise AttributeError(name) if obj is not _missing: From 3830cd5a0d3b10d95c7b717505f266556202a518 Mon Sep 17 00:00:00 2001 From: Susan Su Date: Wed, 13 Feb 2019 18:22:29 -0800 Subject: [PATCH 107/388] bpo-35500: align expected and actual calls on mock.assert_called_with error message. (GH-11804) Backports: 2bdd5858e3f89555c8de73a0f307d63536129dbd Signed-off-by: Chris Withers --- NEWS.d/2019-02-10-00-00-13.bpo-35500.1HOMmo.rst | 1 + mock/mock.py | 7 +++++-- mock/tests/testmock.py | 15 ++++++++------- 3 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 NEWS.d/2019-02-10-00-00-13.bpo-35500.1HOMmo.rst diff --git a/NEWS.d/2019-02-10-00-00-13.bpo-35500.1HOMmo.rst b/NEWS.d/2019-02-10-00-00-13.bpo-35500.1HOMmo.rst new file mode 100644 index 00000000..16b0fbf7 --- /dev/null +++ b/NEWS.d/2019-02-10-00-00-13.bpo-35500.1HOMmo.rst @@ -0,0 +1 @@ +Write expected and actual call parameters on separate lines in :meth:`unittest.mock.Mock.assert_called_with` assertion errors. Contributed by Susan Su. diff --git a/mock/mock.py b/mock/mock.py index bbf23147..8d18e9ba 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -874,7 +874,7 @@ def _format_mock_call_signature(self, args, kwargs): def _format_mock_failure_message(self, args, kwargs): - message = 'Expected call: %s\nActual call: %s' + message = 'expected call not found.\nExpected: %s\nActual: %s' expected_string = self._format_mock_call_signature(args, kwargs) call_args = self.call_args if len(call_args) == 3: @@ -944,7 +944,10 @@ def assert_called_with(_mock_self, *args, **kwargs): self = _mock_self if self.call_args is None: expected = self._format_mock_call_signature(args, kwargs) - raise AssertionError('Expected call: {}\nNot called'.format(expected)) + actual = 'not called.' + error_message = ('expected call not found.\nExpected: %s\nActual: %s' + % (expected, actual)) + raise AssertionError(error_message) def _error_message(cause): msg = self._format_mock_failure_message(args, kwargs) diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index b496a361..c35ae6e8 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -749,7 +749,7 @@ def test_baseexceptional_side_effect(self): def test_assert_called_with_message(self): mock = Mock() - self.assertRaisesRegexp(AssertionError, 'Not called', + self.assertRaisesRegexp(AssertionError, 'not called', mock.assert_called_with) @@ -978,10 +978,11 @@ def assertRaisesWithMsg(self, exception, message, func, *args, **kwargs): def test_assert_called_with_failure_message(self): mock = NonCallableMock() + actual = 'not called.' expected = "mock(1, '2', 3, bar='foo')" - message = 'Expected call: %s\nNot called' + message = 'expected call not found.\nExpected: %s\nActual: %s' self.assertRaisesWithMsg( - AssertionError, message % (expected,), + AssertionError, message % (expected, actual), mock.assert_called_with, 1, '2', 3, bar='foo' ) @@ -994,7 +995,7 @@ def test_assert_called_with_failure_message(self): for meth in asserters: actual = "foo(1, '2', 3, foo='foo')" expected = "foo(1, '2', 3, bar='foo')" - message = 'Expected call: %s\nActual call: %s' + message = 'expected call not found.\nExpected: %s\nActual: %s' self.assertRaisesWithMsg( AssertionError, message % (expected, actual), meth, 1, '2', 3, bar='foo' @@ -1004,7 +1005,7 @@ def test_assert_called_with_failure_message(self): for meth in asserters: actual = "foo(1, '2', 3, foo='foo')" expected = "foo(bar='foo')" - message = 'Expected call: %s\nActual call: %s' + message = 'expected call not found.\nExpected: %s\nActual: %s' self.assertRaisesWithMsg( AssertionError, message % (expected, actual), meth, bar='foo' @@ -1014,7 +1015,7 @@ def test_assert_called_with_failure_message(self): for meth in asserters: actual = "foo(1, '2', 3, foo='foo')" expected = "foo(1, 2, 3)" - message = 'Expected call: %s\nActual call: %s' + message = 'expected call not found.\nExpected: %s\nActual: %s' self.assertRaisesWithMsg( AssertionError, message % (expected, actual), meth, 1, 2, 3 @@ -1024,7 +1025,7 @@ def test_assert_called_with_failure_message(self): for meth in asserters: actual = "foo(1, '2', 3, foo='foo')" expected = "foo()" - message = 'Expected call: %s\nActual call: %s' + message = 'expected call not found.\nExpected: %s\nActual: %s' self.assertRaisesWithMsg( AssertionError, message % (expected, actual), meth ) From 5268edae65d7f840e91f3a689e4269ed5fcd6459 Mon Sep 17 00:00:00 2001 From: Xtreak Date: Mon, 25 Feb 2019 00:24:49 +0530 Subject: [PATCH 108/388] bpo-35512: Resolve string target to patch.dict decorator during function call GH#12000 * Resolve string target to patch.dict during function call * Add NEWS entry * Remove unneeded call * Restore original value for support.target and refactor assertions * Add extra assertion to verify unpatched dict Backports: a875ea58b29fbf510f9790ae1653eeaa47dc0de8 Signed-off-by: Chris Withers Needed some munging due to package layout and code differences. --- NEWS.d/2019-02-24-00-04-10.bpo-35512.eWDjCJ.rst | 3 +++ mock/mock.py | 4 ++-- mock/tests/support.py | 3 +++ mock/tests/testpatch.py | 15 +++++++++++++++ 4 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 NEWS.d/2019-02-24-00-04-10.bpo-35512.eWDjCJ.rst diff --git a/NEWS.d/2019-02-24-00-04-10.bpo-35512.eWDjCJ.rst b/NEWS.d/2019-02-24-00-04-10.bpo-35512.eWDjCJ.rst new file mode 100644 index 00000000..8281b1b2 --- /dev/null +++ b/NEWS.d/2019-02-24-00-04-10.bpo-35512.eWDjCJ.rst @@ -0,0 +1,3 @@ +:func:`unittest.mock.patch.dict` used as a decorator with string target +resolves the target during function call instead of during decorator +construction. Patch by Karthikeyan Singaravelan. diff --git a/mock/mock.py b/mock/mock.py index 8d18e9ba..acd7c28f 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1756,8 +1756,6 @@ class _patch_dict(object): """ def __init__(self, in_dict, values=(), clear=False, **kwargs): - if isinstance(in_dict, basestring): - in_dict = _importer(in_dict) self.in_dict = in_dict # support any argument supported by dict(...) constructor self.values = dict(values) @@ -1798,6 +1796,8 @@ def __enter__(self): def _patch_dict(self): values = self.values + if isinstance(self.in_dict, str): + self.in_dict = _importer(self.in_dict) in_dict = self.in_dict clear = self.clear diff --git a/mock/tests/support.py b/mock/tests/support.py index 2bbc8a24..933be92a 100644 --- a/mock/tests/support.py +++ b/mock/tests/support.py @@ -2,6 +2,9 @@ import sys +target = {'foo': 'FOO'} + + def is_instance(obj, klass): """Version of is_instance that doesn't access __class__""" return issubclass(type(obj), klass) diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index 434e780e..d8857b9a 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -668,6 +668,21 @@ def test(): 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'}) + def test(): + self.assertEqual(support.target, {'foo': 'BAZ', 'bar': 'BAR'}) + try: + support.target = {'foo': 'BAZ'} + test() + self.assertEqual(support.target, {'foo': 'BAZ'}) + finally: + support.target = original + + @unittest.expectedFailure def test_patch_descriptor(self): # would be some effort to fix this - we could special case the From b5903d4fee18336a96b6756f71fa942fd35209a7 Mon Sep 17 00:00:00 2001 From: Xtreak Date: Tue, 26 Feb 2019 03:16:34 +0530 Subject: [PATCH 109/388] Autospec functions should propagate mock calls to parent GH-11273 Backports: 9c3f284de598550be6687964c23fd7599e53b20e Signed-off-by: Chris Withers --- NEWS.d/2018-12-21-09-54-30.bpo-21478.5gsXtc.rst | 2 ++ mock/mock.py | 8 ++++++++ mock/tests/testmock.py | 10 ++++++++++ 3 files changed, 20 insertions(+) create mode 100644 NEWS.d/2018-12-21-09-54-30.bpo-21478.5gsXtc.rst diff --git a/NEWS.d/2018-12-21-09-54-30.bpo-21478.5gsXtc.rst b/NEWS.d/2018-12-21-09-54-30.bpo-21478.5gsXtc.rst new file mode 100644 index 00000000..1000748c --- /dev/null +++ b/NEWS.d/2018-12-21-09-54-30.bpo-21478.5gsXtc.rst @@ -0,0 +1,2 @@ +Calls to a child function created with :func:`unittest.mock.create_autospec` +should propagate to the parent. Patch by Karthikeyan Singaravelan. diff --git a/mock/mock.py b/mock/mock.py index acd7c28f..ec1feced 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -447,6 +447,14 @@ 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 + if not _is_instance_mock(value): return False if ((value._mock_name or value._mock_new_name) or diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index c35ae6e8..2832ebfe 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -1894,6 +1894,16 @@ 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)]) + @unittest.expectedFailure def test_pickle(self): for Klass in (MagicMock, Mock, Subclass, NonCallableMagicMock): From 19b90359e941a86423f852e50731edea9a086974 Mon Sep 17 00:00:00 2001 From: Kumar Akshay Date: Fri, 22 Mar 2019 13:40:40 +0530 Subject: [PATCH 110/388] bpo-21269: Provide args and kwargs attributes on mock call objects GH11807 Backports: b0df45e55dc8304bac0e3cad0225472b84190964 Signed-off-by: Chris Withers --- NEWS.d/2019-02-10-16-49-16.bpo-21269.Fqi7VH.rst | 1 + mock/mock.py | 16 ++++++++++++++++ mock/tests/testhelpers.py | 9 +++++++++ mock/tests/testmock.py | 11 ++++++++--- 4 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 NEWS.d/2019-02-10-16-49-16.bpo-21269.Fqi7VH.rst diff --git a/NEWS.d/2019-02-10-16-49-16.bpo-21269.Fqi7VH.rst b/NEWS.d/2019-02-10-16-49-16.bpo-21269.Fqi7VH.rst new file mode 100644 index 00000000..15ad636a --- /dev/null +++ b/NEWS.d/2019-02-10-16-49-16.bpo-21269.Fqi7VH.rst @@ -0,0 +1 @@ +Add ``args`` and ``kwargs`` properties to mock call objects. Contributed by Kumar Akshay. diff --git a/mock/mock.py b/mock/mock.py index ec1feced..2d529907 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2295,6 +2295,22 @@ def count(self, *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 + else: + name, args, kwargs = self + + return args, kwargs + + @property + def args(self): + return self._get_call_arguments()[0] + + @property + def kwargs(self): + return self._get_call_arguments()[1] + def __repr__(self): if not self._mock_from_kall: name = self._mock_name or 'call' diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index 34a8eae7..8ff276ef 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -158,6 +158,8 @@ def test_call_with_args(self): self.assertEqual(args, ('foo', (1, 2, 3))) self.assertEqual(args, ('foo', (1, 2, 3), {})) self.assertEqual(args, ((1, 2, 3), {})) + self.assertEqual(args.args, (1, 2, 3)) + self.assertEqual(args.kwargs, {}) def test_named_call_with_args(self): @@ -165,6 +167,8 @@ def test_named_call_with_args(self): self.assertEqual(args, ('foo', (1, 2, 3))) self.assertEqual(args, ('foo', (1, 2, 3), {})) + self.assertEqual(args.args, (1, 2, 3)) + self.assertEqual(args.kwargs, {}) self.assertNotEqual(args, ((1, 2, 3),)) self.assertNotEqual(args, ((1, 2, 3), {})) @@ -177,6 +181,8 @@ def test_call_with_kwargs(self): self.assertEqual(args, ('foo', dict(a=3, b=4))) self.assertEqual(args, ('foo', (), dict(a=3, b=4))) self.assertEqual(args, ((), dict(a=3, b=4))) + self.assertEqual(args.args, ()) + self.assertEqual(args.kwargs, dict(a=3, b=4)) def test_named_call_with_kwargs(self): @@ -184,6 +190,8 @@ def test_named_call_with_kwargs(self): self.assertEqual(args, ('foo', dict(a=3, b=4))) self.assertEqual(args, ('foo', (), dict(a=3, b=4))) + self.assertEqual(args.args, ()) + self.assertEqual(args.kwargs, dict(a=3, b=4)) self.assertNotEqual(args, (dict(a=3, b=4),)) self.assertNotEqual(args, ((), dict(a=3, b=4))) @@ -191,6 +199,7 @@ def test_named_call_with_kwargs(self): def test_call_with_args_call_empty_name(self): args = _Call(((1, 2, 3), {})) + self.assertEqual(args, call(1, 2, 3)) self.assertEqual(call(1, 2, 3), args) self.assertIn(call(1, 2, 3), [args]) diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 2832ebfe..ca87e712 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -290,6 +290,10 @@ def test_call(self): self.assertEqual(mock.call_count, 1, "call_count incoreect") self.assertEqual(mock.call_args, ((sentinel.Arg,), {}), "call_args not set") + self.assertEqual(mock.call_args.args, (sentinel.Arg,), + "call_args not set") + self.assertEqual(mock.call_args.kwargs, {}, + "call_args not set") self.assertEqual(mock.call_args_list, [((sentinel.Arg,), {})], "call_args_list not initialised correctly") @@ -323,6 +327,8 @@ def test_call_args_comparison(self): ]) self.assertEqual(mock.call_args, ((sentinel.Arg,), {"kw": sentinel.Kwarg})) + self.assertEqual(mock.call_args.args, (sentinel.Arg,)) + self.assertEqual(mock.call_args.kwargs, {"kw": sentinel.Kwarg}) # Comparing call_args to a long sequence should not raise # an exception. See issue 24857. @@ -1218,9 +1224,8 @@ def test_call_args_two_tuple(self): mock(2, b=4) self.assertEqual(len(mock.call_args), 2) - args, kwargs = mock.call_args - self.assertEqual(args, (2,)) - self.assertEqual(kwargs, dict(b=4)) + self.assertEqual(mock.call_args.args, (2,)) + self.assertEqual(mock.call_args.kwargs, dict(b=4)) expected_list = [((1,), dict(a=3)), ((2,), dict(b=4))] for expected, call_args in zip(expected_list, mock.call_args_list): From e07d77b99e01eebcfecb73e27184e280f0301d2b Mon Sep 17 00:00:00 2001 From: Xtreak Date: Fri, 29 Mar 2019 02:38:43 +0530 Subject: [PATCH 111/388] bpo-36366: Return None on stopping unstarted patch object (GH-12472) Return None after calling unittest.mock.patch.object.stop() regardless of whether the object was started. This makes the method idempotent. https://bugs.python.org/issue36366 Backports: 02b84cb1b4f5407309c81c8b1ae0397355d6e568 Signed-off-by: Chris Withers --- NEWS.d/2019-03-20-15-13-18.bpo-36366.n0eav_.rst | 4 ++++ mock/mock.py | 2 +- mock/tests/testpatch.py | 12 ++++++++++-- 3 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 NEWS.d/2019-03-20-15-13-18.bpo-36366.n0eav_.rst diff --git a/NEWS.d/2019-03-20-15-13-18.bpo-36366.n0eav_.rst b/NEWS.d/2019-03-20-15-13-18.bpo-36366.n0eav_.rst new file mode 100644 index 00000000..a4350483 --- /dev/null +++ b/NEWS.d/2019-03-20-15-13-18.bpo-36366.n0eav_.rst @@ -0,0 +1,4 @@ +Calling ``stop()`` on an unstarted or stopped :func:`unittest.mock.patch` +object will now return `None` instead of raising :exc:`RuntimeError`, +making the method idempotent. +Patch byKarthikeyan Singaravelan. diff --git a/mock/mock.py b/mock/mock.py index 2d529907..a432df8e 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1534,7 +1534,7 @@ def __enter__(self): def __exit__(self, *exc_info): """Undo the patch.""" if not _is_started(self): - raise RuntimeError('stop called on unstarted patcher') + return if self.is_local and self.temp_original is not DEFAULT: setattr(self.target, self.attribute, self.temp_original) diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index d8857b9a..586b1d31 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -774,10 +774,18 @@ def test_patch_start_stop(self): def test_stop_without_start(self): + # bpo-36366: calling stop without start will return None. patcher = patch(foo_name, 'bar', 3) + self.assertIsNone(patcher.stop()) - # calling stop without start used to produce a very obscure error - self.assertRaises(RuntimeError, patcher.stop) + + def test_stop_idempotent(self): + # bpo-36366: calling stop on an already stopped patch will return None. + patcher = patch(foo_name, 'bar', 3) + + patcher.start() + patcher.stop() + self.assertIsNone(patcher.stop()) def test_patchobject_start_stop(self): From 0e50d0d1e6d90d7297548aceae6a5ff9ee62a812 Mon Sep 17 00:00:00 2001 From: Xtreak Date: Sun, 14 Apr 2019 00:42:33 +0530 Subject: [PATCH 112/388] bpo-36593: Fix isinstance check for Mock objects with spec executed under tracing (GH-12790) In Python having a trace function in effect while mock is imported causes isinstance to be wrong for MagicMocks. This is due to the usage of super() in some class methods, as this sets the __class__ attribute. To avoid this, as a workaround, alias the usage of super . Backports: 830b43d03cc47a27a22a50d777f23c8e60820867 Signed-off-by: Chris Withers A lot of munging needed: - the fix was already present as it was needed to get a prior patch to work on Py2. - the test needed adjustment due to the module location differences. --- .../2019-04-11-22-11-24.bpo-36598.hfzDUl.rst | 2 ++ mock/tests/testmock.py | 30 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 NEWS.d/2019-04-11-22-11-24.bpo-36598.hfzDUl.rst diff --git a/NEWS.d/2019-04-11-22-11-24.bpo-36598.hfzDUl.rst b/NEWS.d/2019-04-11-22-11-24.bpo-36598.hfzDUl.rst new file mode 100644 index 00000000..2a798020 --- /dev/null +++ b/NEWS.d/2019-04-11-22-11-24.bpo-36598.hfzDUl.rst @@ -0,0 +1,2 @@ +Fix ``isinstance`` check for Mock objects with spec when the code is +executed under tracing. Patch by Karthikeyan Singaravelan. diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index ca87e712..dd6bfbd2 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -1924,6 +1924,36 @@ def test_pickle(self): self.assertIn('name="foo"', repr(new)) self.assertEqual(new.attribute, 3) + def test_isinstance_under_settrace(self): + # bpo-36593 : __class__ is not set for a class that has __class__ + # property defined when it's used with sys.settrace(trace) set. + # Delete the module to force reimport with tracing function set + # restore the old reference later since there are other tests that are + # 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 + # 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), + old_patch) + with patch.dict('sys.modules'): + del sys.modules['mock.mock'] + def trace(frame, event, arg): + return trace + sys.settrace(trace) + self.addCleanup(sys.settrace, None) + from mock.mock import ( + Mock, MagicMock, NonCallableMock, NonCallableMagicMock + ) + mocks = [ + Mock, MagicMock, NonCallableMock, NonCallableMagicMock + ] + for mock_ in mocks: + obj = mock_(spec=Something) + self.assertIsInstance(obj, Something) + if __name__ == '__main__': unittest.main() From 6039ff3979a1bed5a5737d71dde8d0e91f4a46e1 Mon Sep 17 00:00:00 2001 From: Xtreak Date: Mon, 22 Apr 2019 08:00:23 +0530 Subject: [PATCH 113/388] bpo-23078: Add support for {class,static}method to mock.create_autospec() (GH-11613) Co-authored-by: Felipe Backports: 9b21856b0fcda949de239edc7aa6cf3f2f4f77a3 Signed-off-by: Chris Withers --- .../2019-01-18-23-10-10.bpo-23078.l4dFoj.rst | 2 + mock/mock.py | 4 +- mock/tests/testhelpers.py | 40 ++++++++++++++++++- mock/tests/testmock.py | 17 ++++++++ mock/tests/testpatch.py | 20 ++++++++++ 5 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 NEWS.d/2019-01-18-23-10-10.bpo-23078.l4dFoj.rst diff --git a/NEWS.d/2019-01-18-23-10-10.bpo-23078.l4dFoj.rst b/NEWS.d/2019-01-18-23-10-10.bpo-23078.l4dFoj.rst new file mode 100644 index 00000000..975cc9c0 --- /dev/null +++ b/NEWS.d/2019-01-18-23-10-10.bpo-23078.l4dFoj.rst @@ -0,0 +1,2 @@ +Add support for :func:`classmethod` and :func:`staticmethod` to +:func:`unittest.mock.create_autospec`. Initial patch by Felipe Ochoa. diff --git a/mock/mock.py b/mock/mock.py index a432df8e..5d2b02df 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -62,7 +62,7 @@ import builtins except ImportError: import __builtin__ as builtins -from types import ModuleType +from types import ModuleType, MethodType from unittest.util import safe_repr import six @@ -225,6 +225,8 @@ def _copy_func_details(func, funcopy): def _callable(obj): if isinstance(obj, ClassTypes): return True + if isinstance(obj, (staticmethod, classmethod, MethodType)): + return _callable(obj.__func__) if getattr(obj, '__call__', None) is not None: return True return False diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index 8ff276ef..e44e4ee7 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -13,7 +13,7 @@ call, create_autospec, MagicMock, Mock, ANY, patch, PropertyMock ) -from mock.mock import _Call, _CallList +from mock.mock import _Call, _CallList, _callable from datetime import datetime from functools import partial @@ -1107,5 +1107,43 @@ def test_propertymock_returnvalue(self): self.assertNotIsInstance(returned, PropertyMock) +class TestCallablePredicate(unittest.TestCase): + + def test_type(self): + for obj in [str, bytes, int, list, tuple, SomeClass]: + self.assertTrue(_callable(obj)) + + def test_call_magic_method(self): + class Callable: + def __call__(self): + pass + instance = Callable() + self.assertTrue(_callable(instance)) + + def test_staticmethod(self): + class WithStaticMethod: + @staticmethod + def staticfunc(): + pass + self.assertTrue(_callable(WithStaticMethod.staticfunc)) + + def test_non_callable_staticmethod(self): + class BadStaticMethod: + not_callable = staticmethod(None) + self.assertFalse(_callable(BadStaticMethod.not_callable)) + + def test_classmethod(self): + class WithClassMethod: + @classmethod + def classfunc(cls): + pass + self.assertTrue(_callable(WithClassMethod.classfunc)) + + def test_non_callable_classmethod(self): + class BadClassMethod: + not_callable = classmethod(None) + self.assertFalse(_callable(BadClassMethod.not_callable)) + + if __name__ == '__main__': unittest.main() diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index dd6bfbd2..ae37637b 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -1482,6 +1482,23 @@ def test_create_autospec_with_name(self): m = mock.create_autospec(object(), name='sweet_func') self.assertIn('sweet_func', repr(m)) + #Issue23078 + def test_create_autospec_classmethod_and_staticmethod(self): + class TestClass: + @classmethod + 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() + self.assertRaises(TypeError, mock_method, 'extra_arg') + #Issue21238 def test_mock_unsafe(self): m = Mock() diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index 586b1d31..bbd81fbc 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -55,6 +55,14 @@ def g(self): pass foo = 'bar' + @staticmethod + def static_method(): + return 24 + + @classmethod + def class_method(cls): + return 42 + class Bar(object): def a(self): pass @@ -1025,6 +1033,18 @@ def test(mock_function): self.assertEqual(result, 3) + def test_autospec_staticmethod(self): + with patch('%s.Foo.static_method' % __name__, autospec=True) as method: + Foo.static_method() + method.assert_called_once_with() + + + def test_autospec_classmethod(self): + with patch('%s.Foo.class_method' % __name__, autospec=True) as method: + Foo.class_method() + method.assert_called_once_with() + + def test_autospec_with_new(self): patcher = patch('%s.function' % __name__, new=3, autospec=True) self.assertRaises(TypeError, patcher.start) From cee1b0613006ec388dc2a55b2c0027090c3b7f28 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 30 Apr 2019 08:10:59 +0100 Subject: [PATCH 114/388] no subtest on Py2. --- mock/tests/testmock.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index ae37637b..f727831d 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -1493,7 +1493,6 @@ def class_method(cls): 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() From 5d0f584b048211fa1cc51fdaaf332115b425e4c2 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 30 Apr 2019 08:12:09 +0100 Subject: [PATCH 115/388] latest sync point --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index 840c0ec9..7fbc9b1f 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -20428527a7c188d988d20b267cfef58da10b0fc9 +9b21856b0fcda949de239edc7aa6cf3f2f4f77a3 From 5e72ebe01cd0558c48c170efc577c3dfcc85fae9 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 30 Apr 2019 08:36:13 +0100 Subject: [PATCH 116/388] Deferring fixing this into https://github.com/testing-cabal/mock/issues/452 --- mock/tests/testhelpers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index e44e4ee7..df5c577f 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -977,6 +977,8 @@ def check_data_descriptor(mock_attr): check_data_descriptor(foo.desc) + @unittest.skipIf('PyPy' in sys.version and sys.version_info > (3, 0), + "See https://github.com/testing-cabal/mock/issues/452") def test_autospec_on_bound_builtin_function(self): meth = six.create_bound_method(time.ctime, time.time()) self.assertIsInstance(meth(), str) From a95dca598dffa75cbd8d85a67afd0e303430ca31 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 30 Apr 2019 08:51:37 +0100 Subject: [PATCH 117/388] record where 2.0.0 landed. --- NEWS | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index d69a9b4b..99383050 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,5 @@ -Library -------- +Unreleased +---------- - Issue #28380: unittest.mock Mock autospec functions now properly support assert_called, assert_not_called, and assert_called_once. @@ -24,6 +24,9 @@ Library tuple (changeset 3603bae63c13 only works for classes) so we need to implement __ne__ ourselves. Patch by Andrew Plummer. +2.0.0 +----- + - Issue #26323: Add Mock.assert_called() and Mock.assert_called_once() methods to unittest.mock. Patch written by Amit Saha. From b793c6a87a158948d47470d876c092062f9cc5fc Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 30 Apr 2019 09:11:06 +0100 Subject: [PATCH 118/388] simplify and clean up docs --- NEWS => CHANGELOG.rst | 0 docs/changelog.txt | 5 +++- docs/index.txt | 62 ++++++------------------------------------- 3 files changed, 12 insertions(+), 55 deletions(-) rename NEWS => CHANGELOG.rst (100%) mode change 120000 => 100644 docs/changelog.txt diff --git a/NEWS b/CHANGELOG.rst similarity index 100% rename from NEWS rename to CHANGELOG.rst diff --git a/docs/changelog.txt b/docs/changelog.txt deleted file mode 120000 index 22ec9b8a..00000000 --- a/docs/changelog.txt +++ /dev/null @@ -1 +0,0 @@ -../ChangeLog \ No newline at end of file diff --git a/docs/changelog.txt b/docs/changelog.txt new file mode 100644 index 00000000..03e051e5 --- /dev/null +++ b/docs/changelog.txt @@ -0,0 +1,4 @@ +Changelog from Python's News +============================ + +.. include:: ../CHANGELOG.rst diff --git a/docs/index.txt b/docs/index.txt index 7f4dca20..bbcb74d3 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -2,56 +2,26 @@ Mock - Mocking and Testing Library =================================== -:Version: |release| -:Date: |today| -:Homepage: `Mock Homepage`_ -:Download: `Mock on PyPI`_ -:Documentation: `Python Docs`_ -:License: `BSD License`_ -:Support: `Mailing list (testing-in-python@lists.idyll.org) - `_ -:Issue tracker: `GitHub Issues - `_ - -.. _Mock Homepage: https://github.com/testing-cabal/mock -.. _BSD License: https://github.com/testing-cabal/mock/blob/master/LICENSE.txt -.. _Python Docs: https://docs.python.org/dev/library/unittest.mock.html +.. include:: ../README.rst .. module:: mock :synopsis: Mock object and testing library. .. index:: introduction -TOC -+++ - .. toctree:: - :maxdepth: 2 + :hidden: changelog -Introduction -++++++++++++ - -mock is a library for testing in Python. It allows you to replace parts of -your system under test with mock objects and make assertions about how they -have been used. - -mock is now part of the Python standard library, available as -``unittest.mock`` in Python 3.3 onwards. However, if you are writing code that -runs on multiple versions of Python the ``mock`` package is better, as you get -the newest features from the latest release of Python available for all -Pythons. +Python Version Compatibility +++++++++++++++++++++++++++++ -The ``mock`` package contains a rolling backport of the standard library mock -code compatible with Python 2.7 and 3.4 and up. +* Version 1.0.1 is the last version compatible with Python < 2.6. -* Python 2.6 and 3.3 are supported by mock 2.0.0 and below. +* Version 1.3.0 is the last version compatible with Python 3.2. -* Python 3.2 is supported by mock 1.3.0 and below - with pip no longer - supporting 3.2, we cannot test against that version anymore. - -Please see the standard library documentation for usage details. +* Version 2.0.0 is the last version compatible with Python 2.6. .. index:: installing .. _installing: @@ -59,10 +29,6 @@ Please see the standard library documentation for usage details. Installing ++++++++++ -The current version is |release|. Mock is stable and widely used. - -* `mock on PyPI `_ - .. index:: repository .. index:: git @@ -83,7 +49,6 @@ You can install mock with pip: Bug Reports +++++++++++ -Mock uses `unittest `_ for its own Issues with the backport process, such as compatibility with a particular Python, should be reported to the `bug tracker `_. Feature requests and issues @@ -95,18 +60,7 @@ with Mock functionality should be reported to the `Python bug tracker Python Changes ++++++++++++++ -Python NEWS entries from cPython: - -.. include:: ../NEWS - -.. index:: older versions - -Older Versions of Python -++++++++++++++++++++++++ - -Version 1.0.1 is the last version compatible with Python < 2.6. - -Version 2.0.0 is the last version compatible with Python 2.6. +See the :doc:`change log `. .. index:: maintainer notes From d4f8a7f4173302b658e58701ca5ecb33e43d244f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Tue, 30 Apr 2019 12:17:41 +0200 Subject: [PATCH 119/388] tox.ini: Typo (missing comma) --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index cdef1015..0e1a6a12 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27,pypy,py34,py35,py36,py37jython,docs +envlist = py27,pypy,py34,py35,py36,py37,jython,docs [testenv] commands = From 4dcbc56664babb11d4a3583d8372f81d545149d2 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 30 Apr 2019 16:15:33 +0100 Subject: [PATCH 120/388] remove pbr and replace with simpler to understand and discover code. --- .gitignore | 4 ---- docs/conf.py | 5 +---- mock/mock.py | 6 ++---- requirements.txt | 4 ---- setup.cfg | 3 +++ setup.py | 11 +++++++---- 6 files changed, 13 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index daa8ca64..70d2e1a9 100644 --- a/.gitignore +++ b/.gitignore @@ -12,8 +12,4 @@ runtox *.pyc .testrepository .*.swp -AUTHORS -ChangeLog -.eggs -README.saved README.html diff --git a/docs/conf.py b/docs/conf.py index 6368a01e..d2be5a57 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -71,10 +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. # -# The short X.Y version. -version = mock.mock._v.brief_string() -# The full version, including alpha/beta/rc tags. -release = mock.__version__ +version = release = mock.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) diff --git a/mock/mock.py b/mock/mock.py index 5d2b02df..4bdc70bf 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -67,11 +67,9 @@ import six from six import wraps -from pbr.version import VersionInfo -_v = VersionInfo('mock').semantic_version() -__version__ = _v.release_string() -version_info = _v.version_tuple() +__version__ = '2.0.0' +version_info = tuple(__version__.split('.')) import mock diff --git a/requirements.txt b/requirements.txt index 31bbe5d1..bca9f9bb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,2 @@ funcsigs>=1;python_version<"3.3" -# For runtime needs this is correct. For setup_requires needs, 1.2.0 is needed -# but setuptools can't cope with conflicts in setup_requires, so thats -# unversioned. -pbr>=0.11 six>=1.9 diff --git a/setup.cfg b/setup.cfg index 5597e2fb..02bd9fd1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -29,6 +29,9 @@ classifier = keyword = testing, test, mock, mocking, unittest, patching, stubs, fakes, doubles +[options] +python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*' + [extras] docs = sphinx diff --git a/setup.py b/setup.py index 6154ba71..d47345f0 100755 --- a/setup.py +++ b/setup.py @@ -1,7 +1,10 @@ -#!/usr/bin/env python +import re +from os.path import join + import setuptools setuptools.setup( - setup_requires=['pbr>=1.3', 'setuptools>=17.1'], - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', - pbr=True) + version=re.search("__version__ = '([^']+)'", + open(join('mock', 'mock.py')).read()).group(1), + long_description=open('README.rst').read(), +) From 657d9df623f0f2c3abefbd69ce8531646e22f9ca Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 30 Apr 2019 16:24:50 +0100 Subject: [PATCH 121/388] fix python_requires spelling --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 02bd9fd1..83eb4ee3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,7 +30,7 @@ keyword = testing, test, mock, mocking, unittest, patching, stubs, fakes, doubles [options] -python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*' +python_requires=>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* [extras] docs = From 0e6229ffb51572f0d576c2841607b1965951c9da Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 30 Apr 2019 16:32:29 +0100 Subject: [PATCH 122/388] move setup.cfg to current setuptools standards --- setup.cfg | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/setup.cfg b/setup.cfg index 83eb4ee3..5f50aa87 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,15 +31,13 @@ keyword = [options] python_requires=>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* +packages = mock -[extras] +[options.extras_require] docs = sphinx test = pytest -[files] -packages = mock - [bdist_wheel] universal = 1 From 5792e6f8e7f4b0404ff7b7d32fc0d688754b2a91 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 30 Apr 2019 20:00:10 +0100 Subject: [PATCH 123/388] Trim ready for the release script. --- CHANGELOG.rst | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 99383050..8716d702 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,3 @@ -Unreleased ----------- - - Issue #28380: unittest.mock Mock autospec functions now properly support assert_called, assert_not_called, and assert_called_once. @@ -24,8 +21,8 @@ Unreleased tuple (changeset 3603bae63c13 only works for classes) so we need to implement __ne__ ourselves. Patch by Andrew Plummer. -2.0.0 ------ +2.0.0 and earlier +----------------- - Issue #26323: Add Mock.assert_called() and Mock.assert_called_once() methods to unittest.mock. Patch written by Amit Saha. From b3bc87b69386738579b48fd1ffe19da1d8310609 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 30 Apr 2019 20:00:29 +0100 Subject: [PATCH 124/388] version_info is more useful as a tuple of ints. --- mock/mock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock/mock.py b/mock/mock.py index 4bdc70bf..6c0d1f47 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -69,7 +69,7 @@ from six import wraps __version__ = '2.0.0' -version_info = tuple(__version__.split('.')) +version_info = tuple(int(p) for p in __version__.split('.')) import mock From f8e1a774685384d5e4b5b6af12b2901abbeb00cf Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 30 Apr 2019 20:22:22 +0100 Subject: [PATCH 125/388] release script. --- release.py | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ setup.cfg | 4 +++ 2 files changed, 96 insertions(+) create mode 100644 release.py diff --git a/release.py b/release.py new file mode 100644 index 00000000..2556d509 --- /dev/null +++ b/release.py @@ -0,0 +1,92 @@ +import re +from glob import glob +from os.path import join +from subprocess import call + +import blurb as blurb_module +from argparse import ArgumentParser +from mock import version_info + +VERSION_TYPES = ['major', 'minor', 'bugfix'] + + +def incremented_version(version_info, type_): + type_index = VERSION_TYPES.index(type_) + version_info = tuple(e+(1 if i==type_index else 0) + for i, e in enumerate(version_info)) + return '.'.join(str(p) for p in version_info) + + +def text_from_news(): + # hack: + blurb_module.sections.append('NEWS.d') + + blurbs = blurb_module.Blurbs() + for path in glob(join('NEWS.d', '*')): + blurbs.load_next(path) + + text = [] + for metadata, body in blurbs: + bpo = metadata['bpo'] + body = f"- Issue #{bpo}: " + body + text.append(blurb_module.textwrap_body(body, subsequent_indent=' ')) + + return '\n'.join(text) + + +def news_to_changelog(version): + with open('CHANGELOG.rst') as source: + current_changelog = source.read() + + text = [version] + text.append('-'*len(version)) + text.append('') + text.append(text_from_news()) + text.append(current_changelog) + + new_changelog = '\n'.join(text) + with open('CHANGELOG.rst', 'w') as target: + target.write(new_changelog) + + +def update_version(new_version): + path = join('mock', 'mock.py') + with open(path) as source: + text = source.read() + + text = re.sub("(__version__ = ')[^']+(')", + r"\g<1>"+new_version+r"\2", + text) + + with open(path, 'w') as target: + target.write(text) + + +def git(command): + return call('git '+command, shell=True) + + +def git_commit(new_version): + git('rm NEWS.d/*') + git('add CHANGELOG.rst') + git('add mock/mock.py') + git(f'commit -m "Preparing for {new_version} release."') + + +def parse_args(): + parser = ArgumentParser() + parser.add_argument('type', choices=VERSION_TYPES) + return parser.parse_args() + + +def main(): + args = parse_args() + new_version = incremented_version(version_info, args.type) + news_to_changelog(new_version) + update_version(new_version) + git_commit(new_version) + print(f'{new_version} ready to push, please check the HEAD commit first!') + + +if __name__ == '__main__': + main() diff --git a/setup.cfg b/setup.cfg index 5f50aa87..cde4af1c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -38,6 +38,10 @@ docs = sphinx test = pytest +build = + twine + wheel + blurb [bdist_wheel] universal = 1 From 765defcfcec5ae6302218e7194ad5d3245e896a7 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 30 Apr 2019 20:25:44 +0100 Subject: [PATCH 126/388] Backports: d5d2b4546939b98244708e5bb0cfccd55b99d244, skipped: getfullargspec still needed for Python 2.7 --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index 7fbc9b1f..254ea5f8 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -9b21856b0fcda949de239edc7aa6cf3f2f4f77a3 +d5d2b4546939b98244708e5bb0cfccd55b99d244 From 137b4c59cc2e3d53d0efebe4a1fcbe604c1ebaab Mon Sep 17 00:00:00 2001 From: Mario Corchero Date: Tue, 30 Apr 2019 19:56:36 +0100 Subject: [PATCH 127/388] Don't report deleted attributes in __dir__ (GH#10148) When an attribute is deleted from a Mock, a sentinel is added rather than just deleting the attribute. This commit checks for such sentinels when returning the child mocks in the __dir__ method as users won't expect deleted attributes to appear when performing dir(mock). Backports: 0df635c7f8aa69e56a092bd4f142f0f164741ab2 Signed-off-by: Chris Withers --- NEWS.d/2018-10-27-11-54-12.bpo-35082.HDj1nr.rst | 2 ++ mock/mock.py | 7 +++++-- mock/tests/testmock.py | 9 +++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 NEWS.d/2018-10-27-11-54-12.bpo-35082.HDj1nr.rst diff --git a/NEWS.d/2018-10-27-11-54-12.bpo-35082.HDj1nr.rst b/NEWS.d/2018-10-27-11-54-12.bpo-35082.HDj1nr.rst new file mode 100644 index 00000000..45a07295 --- /dev/null +++ b/NEWS.d/2018-10-27-11-54-12.bpo-35082.HDj1nr.rst @@ -0,0 +1,2 @@ +Don't return deleted attributes when calling dir on a +:class:`unittest.mock.Mock`. diff --git a/mock/mock.py b/mock/mock.py index 6c0d1f47..bd320892 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -809,14 +809,17 @@ def __dir__(self): extras = self._mock_methods or [] from_type = dir(type(self)) from_dict = list(self.__dict__) + from_child_mocks = [ + 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)] - return sorted(set(extras + from_type + from_dict + - list(self._mock_children))) + + return sorted(set(extras + from_type + from_dict + from_child_mocks)) def __setattr__(self, name, value): diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index f727831d..7d697d24 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -946,6 +946,15 @@ def test_filter_dir(self): patcher.stop() + def test_dir_does_not_include_deleted_attributes(self): + mock = Mock() + mock.child.return_value = 1 + + self.assertIn('child', dir(mock)) + del mock.child + self.assertNotIn('child', dir(mock)) + + def test_configure_mock(self): mock = Mock(foo='bar') self.assertEqual(mock.foo, 'bar') From e6fe35178fc1d8a2aef1930a8b47518696b18282 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 30 Apr 2019 20:33:10 +0100 Subject: [PATCH 128/388] Automate lastsync.txt maintenance. --- backport.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/backport.py b/backport.py index 95e136fd..b2ab5237 100644 --- a/backport.py +++ b/backport.py @@ -99,6 +99,12 @@ def skip_current(mock_repo, reason): cleanup_old_patches(mock_repo) +def commit_last_sync(revs, mock_repo): + print('Yay! All caught up!') + if len(revs): + git('commit -m "latest sync point" lastsync.txt', repo=mock_repo) + + def main(): args = parse_args() @@ -124,6 +130,9 @@ def main(): apply_patch(args.mock, rev, patch) break + else: + commit_last_sync(revs, args.mock) + def parse_args(): parser = ArgumentParser() From ab4a27e645af596f5cc60dc554722d585f4949f3 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 30 Apr 2019 20:35:27 +0100 Subject: [PATCH 129/388] latest sync point --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index 254ea5f8..d685aea9 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -d5d2b4546939b98244708e5bb0cfccd55b99d244 +0df635c7f8aa69e56a092bd4f142f0f164741ab2 From ed3bc8b58dfcf9734fd6308c3d82480fa9e8a132 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 1 May 2019 08:48:44 +0100 Subject: [PATCH 130/388] remove jython support from unittest.mock (GH#13033) Backports: 49e27f0afb02ce7b98ed5a4387238850117f4c7e Signed-off-by: Chris Withers --- mock/mock.py | 10 ++-------- mock/tests/testmock.py | 16 ---------------- mock/tests/testpatch.py | 1 - 3 files changed, 2 insertions(+), 25 deletions(-) diff --git a/mock/mock.py b/mock/mock.py index bd320892..a74dcdea 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -106,12 +106,6 @@ def next(obj, _next=_next): _builtins = {name for name in dir(builtins) if not name.startswith('_')} -BaseExceptions = (BaseException,) -if 'java' in sys.platform: - # jython - import java - BaseExceptions = (BaseException, java.lang.Throwable) - try: _isidentifier = str.isidentifier except AttributeError: @@ -140,8 +134,8 @@ def _is_instance_mock(obj): def _is_exception(obj): return ( - isinstance(obj, BaseExceptions) or - isinstance(obj, ClassTypes) and issubclass(obj, BaseExceptions) + isinstance(obj, BaseException) or + isinstance(obj, ClassTypes) and issubclass(obj, BaseException) ) diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 7d697d24..7d545db7 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -207,22 +207,6 @@ def f(): mock.side_effect = ValueError('Bazinga!') self.assertRaisesRegexp(ValueError, 'Bazinga!', mock) - @unittest.skipUnless('java' in sys.platform, - 'This test only applies to Jython') - def test_java_exception_side_effect(self): - import java - mock = Mock(side_effect=java.lang.RuntimeException("Boom!")) - - # can't use assertRaises with java exceptions - try: - mock(1, 2, fish=3) - except java.lang.RuntimeException: - pass - else: - self.fail('java exception not raised') - mock.assert_called_with(1,2, fish=3) - - def test_reset_mock(self): parent = Mock() spec = ["something"] diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index bbd81fbc..a7a433e7 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -1314,7 +1314,6 @@ def test(f, foo): def test_patch_multiple_create_mocks_different_order(self): - # bug revealed by Jython! original_f = Foo.f original_g = Foo.g From f0eb0293249f17687bc80a174f331bc077f78bf4 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 1 May 2019 23:04:04 +0100 Subject: [PATCH 131/388] Mock 100% coverage (GH-13045) This was achieved by: * moving many pass statements in tests onto their own lines, so they pass line coverage and can match an easy ignore pattern if branch coverage is added later. * removing code that cannot be reached. * removing long-disabled tests. * removing unused code. * adding tests for uncovered code It turned out that removing `if __name__ == '__main__'` blocks that run unittest.main() at the bottom of test files was surprisingly contentious, so they remain and can be filtered out with an appropriate .coveragerc. Backports: adbf178e49113b2de0042e86a1228560475a65c5 Signed-off-by: Chris Withers Some alterations had to be made for Py2 compatibility. --- mock/mock.py | 56 +--------- mock/tests/support.py | 3 +- mock/tests/testcallable.py | 3 +- mock/tests/testhelpers.py | 186 +++++++++++++++++++-------------- mock/tests/testhelpers_py3.py | 1 + mock/tests/testmagicmethods.py | 6 +- mock/tests/testmock.py | 130 +++++++++++++---------- mock/tests/testpatch.py | 163 +++++++++++------------------ mock/tests/testsealable.py | 9 +- mock/tests/testwith.py | 13 +-- 10 files changed, 259 insertions(+), 311 deletions(-) diff --git a/mock/mock.py b/mock/mock.py index a74dcdea..c6a770f4 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -259,8 +259,6 @@ def _set_signature(mock, original, instance=False): # creates a function with signature (*args, **kwargs) that delegates to a # mock. It still does signature checking by calling a lambda with the same # signature as the original. - if not _callable(original): - return skipfirst = isinstance(original, ClassTypes) result = _get_signature_object(original, instance, skipfirst) @@ -287,10 +285,6 @@ def checksig(*args, **kwargs): def _setup_func(funcopy, mock, sig): funcopy.mock = mock - # can't use isinstance with mocks - if not _is_instance_mock(mock): - return - def assert_called(*args, **kwargs): return mock.assert_called(*args, **kwargs) def assert_not_called(*args, **kwargs): @@ -384,12 +378,6 @@ class OldStyleClass: ClassType = type(OldStyleClass) -def _copy(value): - if type(value) in (dict, list, tuple, set): - return type(value)(value) - return value - - ClassTypes = (type,) if six.PY2: ClassTypes = (type, ClassType) @@ -476,8 +464,6 @@ def _check_and_set_parent(parent, value, name, new_name): class _MockIter(object): def __init__(self, obj): self.obj = iter(obj) - def __iter__(self): - return self def __next__(self): return next(self.obj) @@ -577,7 +563,7 @@ def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False, if isinstance(spec, ClassTypes): _spec_class = spec else: - _spec_class = _get_class(spec) + _spec_class = type(spec) res = _get_signature_object(spec, _spec_as_instance, _eat_self) _spec_signature = res and res[1] @@ -749,7 +735,7 @@ def _extract_mock_name(self): dot = '.' if _name_list == ['()']: dot = '' - seen = set() + while _parent is not None: last = _parent @@ -760,11 +746,6 @@ def _extract_mock_name(self): _parent = _parent._mock_new_parent - # use ids here so as not to call __hash__ on the mocks - if id(_parent) in seen: - break - seen.add(id(_parent)) - _name_list = list(reversed(_name_list)) _first = last._mock_name or 'mock' if len(_name_list) > 1: @@ -882,8 +863,6 @@ def _format_mock_failure_message(self, args, kwargs): message = 'expected call not found.\nExpected: %s\nActual: %s' expected_string = self._format_mock_call_signature(args, kwargs) call_args = self.call_args - if len(call_args) == 3: - call_args = call_args[1:] actual_string = self._format_mock_call_signature(*call_args) return message % (expected_string, actual_string) @@ -1125,8 +1104,6 @@ def _mock_call(_mock_self, *args, **kwargs): self.call_args = _call self.call_args_list.append(_call) - seen = set() - # initial stuff for method_calls: do_method_calls = self._mock_parent is not None method_call_name = self._mock_name @@ -1162,13 +1139,6 @@ def _mock_call(_mock_self, *args, **kwargs): # follow the parental chain: _new_parent = _new_parent._mock_new_parent - # check we're not in an infinite loop: - # ( use ids here so as not to call __hash__ on the mocks) - _new_parent_id = id(_new_parent) - if _new_parent_id in seen: - break - seen.add(_new_parent_id) - effect = self.side_effect if effect is not None: if _is_exception(effect): @@ -2007,12 +1977,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? - return_value = AttributeError(name) + return_value = return_calulator(mock) method.return_value = return_value return @@ -2092,10 +2057,6 @@ def __init__(self, name, parent): self.name = name self.parent = parent - def __call__(self, *args, **kwargs): - m = self.create_mock() - return m(*args, **kwargs) - def create_mock(self): entry = self.name parent = self.parent @@ -2499,19 +2460,10 @@ def _must_skip(spec, entry, is_type): else: return False - # shouldn't get here unless function is a dynamically provided attribute - # XXXX untested behaviour + # function is a dynamically provided attribute return is_type -def _get_class(obj): - try: - return obj.__class__ - except AttributeError: - # it is possible for objects to have no __class__ - return type(obj) - - class _SpecState(object): def __init__(self, spec, spec_set=False, parent=None, diff --git a/mock/tests/support.py b/mock/tests/support.py index 933be92a..d57a372b 100644 --- a/mock/tests/support.py +++ b/mock/tests/support.py @@ -13,8 +13,7 @@ def is_instance(obj, klass): class SomeClass(object): class_attribute = None - def wibble(self): - pass + def wibble(self): pass class X(object): diff --git a/mock/tests/testcallable.py b/mock/tests/testcallable.py index 03c8929d..30e1f26b 100644 --- a/mock/tests/testcallable.py +++ b/mock/tests/testcallable.py @@ -98,8 +98,7 @@ def test_patch_spec_set_instance(self): def test_patch_spec_callable_class(self): class CallableX(X): - def __call__(self): - pass + def __call__(self): pass class Sub(CallableX): pass diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index df5c577f..3b86cecd 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -24,12 +24,9 @@ class SomeClass(object): - def one(self, a, b): - pass - def two(self): - pass - def three(self, a=None): - pass + def one(self, a, b): pass + def two(self): pass + def three(self, a=None): pass @@ -60,12 +57,9 @@ def test_any_and_datetime(self): def test_any_mock_calls_comparison_order(self): mock = Mock() - d = datetime.now() class Foo(object): - def __eq__(self, other): - return False - def __ne__(self, other): - return True + def __eq__(self, other): pass + def __ne__(self, other): pass for d in datetime.now(), Foo(): mock.reset_mock() @@ -390,8 +384,7 @@ def test_basic(self): def test_create_autospec_return_value(self): - def f(): - pass + def f(): pass mock = create_autospec(f, return_value='foo') self.assertEqual(mock(), 'foo') @@ -411,8 +404,7 @@ def test_autospec_reset_mock(self): def test_mocking_unbound_methods(self): class Foo(object): - def foo(self, foo): - pass + def foo(self, foo): pass p = patch.object(Foo, 'foo') mock_foo = p.start() Foo().foo(1) @@ -420,23 +412,6 @@ def foo(self, foo): mock_foo.assert_called_with(1) - @unittest.expectedFailure - def test_create_autospec_unbound_methods(self): - # see mock issue 128 - class Foo(object): - def foo(self): - pass - - klass = create_autospec(Foo) - instance = klass() - self.assertRaises(TypeError, instance.foo, 1) - - # Note: no type checking on the "self" parameter - klass.foo(1) - klass.foo.assert_called_with(1) - self.assertRaises(TypeError, klass.foo) - - def test_create_autospec_keyword_arguments(self): class Foo(object): a = 3 @@ -445,7 +420,7 @@ class Foo(object): @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):\n pass\n" + func_def = "def foo(a, *, b=None): pass\n" namespace = {} exec (func_def, namespace) foo = namespace['foo'] @@ -460,8 +435,7 @@ def test_create_autospec_keyword_only_arguments(self): def test_function_as_instance_attribute(self): obj = SomeClass() - def f(a): - pass + def f(a): pass obj.f = f mock = create_autospec(obj) @@ -497,6 +471,45 @@ class Sub(SomeClass): self._check_someclass_mock(mock) + 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) + mock.some_attr(1) + with self.assertRaises(TypeError): + mock.some_attr() + 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'] + + def __getattr__(self, item): + if item == 'crazy': + return lambda x: x + raise AttributeError(item) + + inst = CrazyClass() + with self.assertRaises(AttributeError): + inst.other + self.assertEqual(inst.crazy(42), 42) + mock = create_autospec(inst) + mock.crazy(42) + with self.assertRaises(TypeError): + mock.crazy() + with self.assertRaises(TypeError): + 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): @@ -504,8 +517,7 @@ def test_builtin_functions_types(self): # with *args / **kwargs signature. Using the builtin method type # as a spec seems to work fairly well though. class BuiltinSubclass(list): - def bar(self, arg): - pass + def bar(self, arg): pass sorted = sorted attr = {} @@ -579,17 +591,13 @@ class Sub(SomeClass): def test_descriptors(self): class Foo(object): @classmethod - def f(cls, a, b): - pass + def f(cls, a, b): pass @staticmethod - def g(a, b): - pass + def g(a, b): pass - class Bar(Foo): - pass + class Bar(Foo): pass - class Baz(SomeClass, Bar): - pass + class Baz(SomeClass, Bar): pass for spec in (Foo, Foo(), Bar, Bar(), Baz, Baz()): mock = create_autospec(spec) @@ -624,8 +632,7 @@ class Bar(Foo): def test_recursive(self): class A(object): - def a(self): - pass + def a(self): pass foo = 'foo bar baz' bar = foo @@ -647,11 +654,9 @@ def a(self): def test_spec_inheritance_for_classes(self): class Foo(object): - def a(self, x): - pass + def a(self, x): pass class Bar(object): - def f(self, y): - pass + def f(self, y): pass class_mock = create_autospec(Foo) @@ -731,8 +736,7 @@ def test_builtins(self): def test_function(self): - def f(a, b): - pass + def f(a, b): pass mock = create_autospec(f) self.assertRaises(TypeError, mock) @@ -762,9 +766,10 @@ class RaiserClass(object): def existing(a, b): return a + b + self.assertEqual(RaiserClass.existing(1, 2), 3) s = create_autospec(RaiserClass) self.assertRaises(TypeError, lambda x: s.existing(1, 2, 3)) - s.existing(1, 2) + self.assertEqual(s.existing(1, 2), s.existing.return_value) self.assertRaises(AttributeError, lambda: s.nonexisting) # check we can fetch the raiser attribute and it has no spec @@ -774,8 +779,7 @@ def existing(a, b): def test_signature_class(self): class Foo(object): - def __init__(self, a, b=3): - pass + def __init__(self, a, b=3): pass mock = create_autospec(Foo) @@ -826,10 +830,8 @@ class Foo: def test_signature_callable(self): class Callable(object): - def __init__(self, x, y): - pass - def __call__(self, a): - pass + def __init__(self, x, y): pass + def __call__(self, a): pass mock = create_autospec(Callable) mock(1, 2) @@ -885,8 +887,7 @@ class Foo(object): def test_autospec_functions_with_self_in_odd_place(self): class Foo(object): - def f(a, self): - pass + def f(a, self): pass a = create_autospec(Foo) a.f(10) @@ -934,12 +935,9 @@ def __init__(self, value): self.value = value def __get__(self, obj, cls=None): - if obj is None: - return self - return self.value + return self - def __set__(self, obj, value): - pass + def __set__(self, obj, value): pass class MyProperty(property): pass @@ -948,12 +946,10 @@ class Foo(object): __slots__ = ['slot'] @property - def prop(self): - return 3 + def prop(self): pass @MyProperty - def subprop(self): - return 4 + def subprop(self): pass desc = Descriptor(42) @@ -1009,8 +1005,7 @@ def __getattr__(self, attribute): def test_spec_inspect_signature(self): - def myfunc(x, y): - pass + def myfunc(x, y): pass mock = create_autospec(myfunc) mock(1, 2) @@ -1024,6 +1019,42 @@ def myfunc(x, y): 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_function_assert_has_calls(self): + def f(a): pass + mock = create_autospec(f) + mock(1) + mock.assert_has_calls([call(1)]) + with self.assertRaises(AssertionError): + mock.assert_has_calls([call(2)]) + + + def test_spec_function_assert_any_call(self): + def f(a): pass + mock = create_autospec(f) + mock(1) + mock.assert_any_call(1) + with self.assertRaises(AssertionError): + mock.assert_any_call(2) + + + def test_spec_function_reset_mock(self): + def f(a): pass + rv = Mock() + mock = create_autospec(f, return_value=rv) + mock(1)(2) + self.assertEqual(mock.mock_calls, [call(1)]) + self.assertEqual(rv.mock_calls, [call(2)]) + mock.reset_mock() + self.assertEqual(mock.mock_calls, []) + self.assertEqual(rv.mock_calls, []) + + class TestCallList(unittest.TestCase): def test_args_list_contains_call_list(self): @@ -1117,16 +1148,14 @@ def test_type(self): def test_call_magic_method(self): class Callable: - def __call__(self): - pass + def __call__(self): pass instance = Callable() self.assertTrue(_callable(instance)) def test_staticmethod(self): class WithStaticMethod: @staticmethod - def staticfunc(): - pass + def staticfunc(): pass self.assertTrue(_callable(WithStaticMethod.staticfunc)) def test_non_callable_staticmethod(self): @@ -1137,8 +1166,7 @@ class BadStaticMethod: def test_classmethod(self): class WithClassMethod: @classmethod - def classfunc(cls): - pass + def classfunc(cls): pass self.assertTrue(_callable(WithClassMethod.classfunc)) def test_non_callable_classmethod(self): diff --git a/mock/tests/testhelpers_py3.py b/mock/tests/testhelpers_py3.py index 2af91b50..64d62f89 100644 --- a/mock/tests/testhelpers_py3.py +++ b/mock/tests/testhelpers_py3.py @@ -12,6 +12,7 @@ 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) diff --git a/mock/tests/testmagicmethods.py b/mock/tests/testmagicmethods.py index d3d2d7c5..f6c25fbc 100644 --- a/mock/tests/testmagicmethods.py +++ b/mock/tests/testmagicmethods.py @@ -375,8 +375,7 @@ def test_magic_methods_fspath(self): def test_magic_methods_and_spec(self): class Iterable(object): - def __iter__(self): - pass + def __iter__(self): pass mock = Mock(spec=Iterable) self.assertRaises(AttributeError, lambda: mock.__iter__) @@ -400,8 +399,7 @@ def set_int(): def test_magic_methods_and_spec_set(self): class Iterable(object): - def __iter__(self): - pass + def __iter__(self): pass mock = Mock(spec_set=Iterable) self.assertRaises(AttributeError, lambda: mock.__iter__) diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 7d545db7..cb1cc66c 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -41,16 +41,13 @@ def next(self): class Something(object): - def meth(self, a, b, c, d=None): - pass + def meth(self, a, b, c, d=None): pass @classmethod - def cmeth(cls, a, b, c, d=None): - pass + def cmeth(cls, a, b, c, d=None): pass @staticmethod - def smeth(a, b, c, d=None): - pass + def smeth(a, b, c, d=None): pass class Subclass(MagicMock): @@ -106,6 +103,21 @@ def test_return_value_in_constructor(self): "return value in constructor not honoured") + def test_change_return_value_via_delegate(self): + def f(): pass + mock = create_autospec(f) + mock.mock.return_value = 1 + self.assertEqual(mock(), 1) + + + def test_change_side_effect_via_delegate(self): + def f(): pass + mock = create_autospec(f) + mock.mock.side_effect = TypeError() + with self.assertRaises(TypeError): + mock() + + def test_repr(self): mock = Mock(name='foo') self.assertIn('foo', repr(mock)) @@ -184,8 +196,7 @@ def test_autospec_side_effect(self): results = [1, 2, 3] def effect(): return results.pop() - def f(): - pass + def f(): pass mock = create_autospec(f) mock.side_effect = [1, 2, 3] @@ -200,8 +211,7 @@ def f(): def test_autospec_side_effect_exception(self): # Test for issue 23661 - def f(): - pass + def f(): pass mock = create_autospec(f) mock.side_effect = ValueError('Bazinga!') @@ -362,8 +372,7 @@ def test_assert_called_with_any(self): def test_assert_called_with_function_spec(self): - def f(a, b, c, d=None): - pass + def f(a, b, c, d=None): pass mock = Mock(spec=f) @@ -432,8 +441,7 @@ def test_assert_called_once_with_call_list(self): def test_assert_called_once_with_function_spec(self): - def f(a, b, c, d=None): - pass + def f(a, b, c, d=None): pass mock = Mock(spec=f) @@ -538,8 +546,7 @@ def test_from_spec(self): class Something(object): x = 3 __something__ = None - def y(self): - pass + def y(self): pass def test_attributes(mock): # should work @@ -625,8 +632,7 @@ def method(self): def test_customize_wrapped_object_with_side_effect_iterable(self): class Real(object): - def method(self): - raise NotImplementedError() + def method(self): pass real = Real() mock = Mock(wraps=real) @@ -639,8 +645,7 @@ def method(self): def test_customize_wrapped_object_with_side_effect_exception(self): class Real(object): - def method(self): - raise NotImplementedError() + def method(self): pass real = Real() mock = Mock(wraps=real) @@ -651,9 +656,7 @@ def method(self): def test_customize_wrapped_object_with_side_effect_function(self): class Real(object): - def method(self): - raise NotImplementedError() - + def method(self): pass def side_effect(): return sentinel.VALUE @@ -666,8 +669,7 @@ def side_effect(): def test_customize_wrapped_object_with_return_value(self): class Real(object): - def method(self): - raise NotImplementedError() + def method(self): pass real = Real() mock = Mock(wraps=real) @@ -679,8 +681,7 @@ def method(self): def test_customize_wrapped_object_with_return_value_and_side_effect(self): # side_effect should always take precedence over return_value. class Real(object): - def method(self): - raise NotImplementedError() + def method(self): pass real = Real() mock = Mock(wraps=real) @@ -695,8 +696,7 @@ def method(self): def test_customize_wrapped_object_with_return_value_and_side_effect2(self): # side_effect can return DEFAULT to default to return_value class Real(object): - def method(self): - raise NotImplementedError() + def method(self): pass real = Real() mock = Mock(wraps=real) @@ -708,8 +708,7 @@ def method(self): def test_customize_wrapped_object_with_return_value_and_side_effect_default(self): class Real(object): - def method(self): - raise NotImplementedError() + def method(self): pass real = Real() mock = Mock(wraps=real) @@ -788,6 +787,30 @@ class X(object): self.assertIsInstance(mock, X) + def test_spec_class_no_object_base(self): + class X: + pass + + 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) + + self.assertIs(mock.__class__, X) + self.assertEqual(Mock().__class__.__name__, 'Mock') + + 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) + + def test_setting_attribute_with_spec_set(self): class X(object): y = 3 @@ -962,15 +985,9 @@ def test_configure_mock(self): def assertRaisesWithMsg(self, exception, message, func, *args, **kwargs): # needed because assertRaisesRegex doesn't work easily with newlines - try: + with self.assertRaises(exception) as context: func(*args, **kwargs) - except: - instance = sys.exc_info()[1] - self.assertIsInstance(instance, exception) - else: - self.fail('Exception {!r} not raised'.format(exception)) - - msg = str(instance) + msg = str(context.exception) self.assertEqual(msg, message) @@ -1159,6 +1176,18 @@ def test_mock_call_repr(self): self.assertEqual(repr(m.mock_calls[2]), 'call.foo().bar().baz.bob()') + def test_mock_call_repr_loop(self): + m = Mock() + m.foo = m + repr(m.foo()) + self.assertRegexpMatches(repr(m.foo()), r"") + + + def test_mock_calls_contains(self): + m = Mock() + self.assertFalse([call()] in m.mock_calls) + + def test_subclassing(self): class Subclass(Mock): pass @@ -1373,8 +1402,7 @@ def test_assert_has_calls(self): def test_assert_has_calls_with_function_spec(self): - def f(a, b, c, d=None): - pass + def f(a, b, c, d=None): pass mock = Mock(spec=f) @@ -1432,8 +1460,7 @@ def test_assert_any_call(self): def test_assert_any_call_with_function_spec(self): - def f(a, b, c, d=None): - pass + def f(a, b, c, d=None): pass mock = Mock(spec=f) @@ -1453,8 +1480,7 @@ def f(a, b, c, d=None): def test_mock_calls_create_autospec(self): - def f(a, b): - pass + def f(a, b): pass obj = Iter() obj.f = f @@ -1479,12 +1505,10 @@ def test_create_autospec_with_name(self): def test_create_autospec_classmethod_and_staticmethod(self): class TestClass: @classmethod - def class_method(cls): - pass + def class_method(cls): pass @staticmethod - def static_method(): - pass + def static_method(): pass for method in ('class_method', 'static_method'): mock_method = mock.create_autospec(getattr(TestClass, method)) mock_method() @@ -1909,8 +1933,7 @@ def test_parent_attribute_of_call(self): self.assertEqual(type(call.parent().parent), _Call) def test_parent_propagation_with_create_autospec(self): - def foo(a, b): - pass + def foo(a, b): pass mock = Mock() mock.child = create_autospec(foo) @@ -1949,10 +1972,11 @@ def test_isinstance_under_settrace(self): old_patch) with patch.dict('sys.modules'): del sys.modules['mock.mock'] - def trace(frame, event, arg): + # 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) - self.addCleanup(sys.settrace, None) from mock.mock import ( Mock, MagicMock, NonCallableMock, NonCallableMagicMock ) diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index a7a433e7..399961fb 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -47,31 +47,24 @@ def __delattr__(self, name): class Foo(object): - def __init__(self, a): - pass - def f(self, a): - pass - def g(self): - pass + def __init__(self, a): pass + def f(self, a): pass + def g(self): pass foo = 'bar' @staticmethod - def static_method(): - return 24 + def static_method(): pass @classmethod - def class_method(cls): - return 42 + def class_method(cls): pass class Bar(object): - def a(self): - pass + def a(self): pass foo_name = '%s.Foo' % __name__ -def function(a, b=Foo): - pass +def function(a, b=Foo): pass class Container(object): @@ -374,31 +367,19 @@ def test(): def test_patch_wont_create_by_default(self): - try: + with self.assertRaises(AttributeError): @patch('%s.frooble' % builtin_string, sentinel.Frooble) - def test(): - self.assertEqual(frooble, sentinel.Frooble) + def test(): pass test() - except AttributeError: - pass - else: - self.fail('Patching non existent attributes should fail') - self.assertRaises(NameError, lambda: frooble) def test_patchobject_wont_create_by_default(self): - try: + with self.assertRaises(AttributeError): @patch.object(SomeClass, 'ord', sentinel.Frooble) - def test(): - self.fail('Patching non existent attributes should fail') - + def test(): pass test() - except AttributeError: - pass - else: - self.fail('Patching non existent attributes should fail') self.assertFalse(hasattr(SomeClass, 'ord')) @@ -488,6 +469,9 @@ class Something(object): attribute = sentinel.Original class Foo(object): + + test_class_attr = 'whatever' + def test_method(other_self, mock_something): self.assertEqual(PTModule.something, mock_something, "unpatched") @@ -646,8 +630,7 @@ def test_name_preserved(self): @patch('%s.SomeClass' % __name__, object(), autospec=True) @patch.object(SomeClass, object()) @patch.dict(foo) - def some_name(): - pass + def some_name(): pass self.assertEqual(some_name.__name__, 'some_name') @@ -658,12 +641,9 @@ def test_patch_with_exception(self): @patch.dict(foo, {'a': 'b'}) def test(): raise NameError('Konrad') - try: + + with self.assertRaises(NameError): test() - except NameError: - pass - else: - self.fail('NameError not raised by test') self.assertEqual(foo, {}) @@ -691,49 +671,6 @@ def test(): support.target = original - @unittest.expectedFailure - def test_patch_descriptor(self): - # would be some effort to fix this - we could special case the - # builtin descriptors: classmethod, property, staticmethod - class Nothing(object): - foo = None - - class Something(object): - foo = {} - - @patch.object(Nothing, 'foo', 2) - @classmethod - def klass(cls): - self.assertIs(cls, Something) - - @patch.object(Nothing, 'foo', 2) - @staticmethod - def static(arg): - return arg - - @patch.dict(foo) - @classmethod - def klass_dict(cls): - self.assertIs(cls, Something) - - @patch.dict(foo) - @staticmethod - def static_dict(arg): - return arg - - # these will raise exceptions if patching descriptors is broken - self.assertEqual(Something.static('f00'), 'f00') - Something.klass() - self.assertEqual(Something.static_dict('f00'), 'f00') - Something.klass_dict() - - something = Something() - self.assertEqual(something.static('f00'), 'f00') - something.klass() - self.assertEqual(something.static_dict('f00'), 'f00') - something.klass_dict() - - def test_patch_spec_set(self): @patch('%s.SomeClass' % __name__, spec_set=SomeClass) def test(MockClass): @@ -933,17 +870,13 @@ def test_patch_dict_keyword_args(self): def test_autospec(self): class Boo(object): - def __init__(self, a): - pass - def f(self, a): - pass - def g(self): - pass + def __init__(self, a): pass + def f(self, a): pass + def g(self): pass foo = 'bar' class Bar(object): - def a(self): - pass + def a(self): pass def _test(mock): mock(1) @@ -1490,20 +1423,17 @@ def test_nested_patch_failure(self): @patch.object(Foo, 'g', 1) @patch.object(Foo, 'missing', 1) @patch.object(Foo, 'f', 1) - def thing1(): - pass + def thing1(): pass @patch.object(Foo, 'missing', 1) @patch.object(Foo, 'g', 1) @patch.object(Foo, 'f', 1) - def thing2(): - pass + def thing2(): pass @patch.object(Foo, 'g', 1) @patch.object(Foo, 'f', 1) @patch.object(Foo, 'missing', 1) - def thing3(): - pass + def thing3(): pass for func in thing1, thing2, thing3: self.assertRaises(AttributeError, func) @@ -1522,20 +1452,17 @@ def crasher(): @patch.object(Foo, 'g', 1) @patch.object(Foo, 'foo', new_callable=crasher) @patch.object(Foo, 'f', 1) - def thing1(): - pass + def thing1(): pass @patch.object(Foo, 'foo', new_callable=crasher) @patch.object(Foo, 'g', 1) @patch.object(Foo, 'f', 1) - def thing2(): - pass + def thing2(): pass @patch.object(Foo, 'g', 1) @patch.object(Foo, 'f', 1) @patch.object(Foo, 'foo', new_callable=crasher) - def thing3(): - pass + def thing3(): pass for func in thing1, thing2, thing3: self.assertRaises(NameError, func) @@ -1561,8 +1488,7 @@ def test_patch_multiple_failure(self): patcher.additional_patchers = additionals @patcher - def func(): - pass + def func(): pass self.assertRaises(AttributeError, func) self.assertEqual(Foo.f, original_f) @@ -1590,8 +1516,7 @@ def crasher(): patcher.additional_patchers = additionals @patcher - def func(): - pass + def func(): pass self.assertRaises(NameError, func) self.assertEqual(Foo.f, original_f) @@ -1931,5 +1856,35 @@ def foo(x=0): 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 + # 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'] + # now make sure we can patch based on a dotted path: + @patch('mock.tests.support.X') + def test(mock): + pass + test() + + + def test_invalid_target(self): + with self.assertRaises(TypeError): + patch('') + + + def test_cant_set_kwargs_when_passing_a_mock(self): + @patch('mock.tests.support.X', new=object(), x=1) + def test(): pass + with self.assertRaises(TypeError): + test() + + if __name__ == '__main__': unittest.main() diff --git a/mock/tests/testsealable.py b/mock/tests/testsealable.py index bd271421..63a85414 100644 --- a/mock/tests/testsealable.py +++ b/mock/tests/testsealable.py @@ -3,15 +3,10 @@ class SampleObject: - def __init__(self): - self.attr_sample1 = 1 - self.attr_sample2 = 1 - def method_sample1(self): - pass + def method_sample1(self): pass - def method_sample2(self): - pass + def method_sample2(self): pass class TestSealable(unittest.TestCase): diff --git a/mock/tests/testwith.py b/mock/tests/testwith.py index ce6e08c0..31e8322b 100644 --- a/mock/tests/testwith.py +++ b/mock/tests/testwith.py @@ -14,6 +14,8 @@ something_else = sentinel.SomethingElse +class SampleException(Exception): pass + class WithTest(unittest.TestCase): @@ -24,14 +26,10 @@ def test_with_statement(self): def test_with_statement_exception(self): - try: + with self.assertRaises(SampleException): with patch('%s.something' % __name__, sentinel.Something2): self.assertEqual(something, sentinel.Something2, "unpatched") - raise Exception('pow') - except Exception: - pass - else: - self.fail("patch swallowed exception") + raise SampleException() self.assertEqual(something, sentinel.Something) @@ -133,8 +131,7 @@ def test_dict_context_manager(self): def test_double_patch_instance_method(self): class C: - def f(self): - pass + def f(self): pass c = C() From 40b44b1cf6fca0f32560608e5cd2f3743d81d038 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 1 May 2019 23:38:26 +0100 Subject: [PATCH 132/388] latest sync point --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index d685aea9..8715e2e8 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -0df635c7f8aa69e56a092bd4f142f0f164741ab2 +adbf178e49113b2de0042e86a1228560475a65c5 From 6ce5f0d712235405024139a006daf277d98b6aa0 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 2 May 2019 00:14:50 +0100 Subject: [PATCH 133/388] move to .readthedocs.yml --- .readthedocs.yml | 8 ++++++++ requirements.txt | 2 -- 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 .readthedocs.yml delete mode 100644 requirements.txt diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 00000000..7687b8a8 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,8 @@ +version: 2 +python: + version: 3.7 + install: + - method: pip + path: . + extra_requirements: + - docs diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index bca9f9bb..00000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -funcsigs>=1;python_version<"3.3" -six>=1.9 From 0459be22861214bef07c6a707d09ad22fec5af35 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 2 May 2019 00:17:02 +0100 Subject: [PATCH 134/388] remove jython support for now, we can bring it back if there's demand --- docs/index.txt | 6 +++--- setup.cfg | 1 - tox.ini | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/index.txt b/docs/index.txt index bbcb74d3..d027ecf0 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -23,6 +23,8 @@ Python Version Compatibility * Version 2.0.0 is the last version compatible with Python 2.6. +* Version 2.0.0 is the last version offering official Jython support. + .. index:: installing .. _installing: @@ -76,9 +78,7 @@ 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, nightly Python 3 builds, pypy, pypy3. Jython support is desired, if -someone could contribute a patch to .travis.yml to support it that would be -excellent. +3.5, 3.6, nightly Python 3 builds, pypy, pypy3. Releasing --------- diff --git a/setup.cfg b/setup.cfg index cde4af1c..39862cd4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -21,7 +21,6 @@ classifier = Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: Implementation :: CPython - Programming Language :: Python :: Implementation :: Jython Programming Language :: Python :: Implementation :: PyPy Topic :: Software Development :: Libraries Topic :: Software Development :: Libraries :: Python Modules diff --git a/tox.ini b/tox.ini index 0e1a6a12..90ca455d 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27,pypy,py34,py35,py36,py37,jython,docs +envlist = py27,pypy,py34,py35,py36,py37,docs [testenv] commands = From 252c749d7ea2e18f67618335f715b825d92fd5db Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 2 May 2019 01:28:27 +0100 Subject: [PATCH 135/388] make magic mocks work on pypy again --- mock/mock.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/mock/mock.py b/mock/mock.py index c6a770f4..27fe9ab3 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1977,7 +1977,13 @@ def _set_return_value(mock, method, name): return_calulator = _calculate_return_value.get(name) if return_calulator is not None: - return_value = return_calulator(mock) + 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) method.return_value = return_value return From a5ed43f83ad073cdc24e9673e404a1d15e690775 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 1 May 2019 23:46:34 +0100 Subject: [PATCH 136/388] this also fails on pypy as per https://github.com/testing-cabal/mock/issues/452 --- mock/tests/testhelpers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index 3b86cecd..228c0c64 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -471,6 +471,9 @@ 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_): From 0867420f6a299e69bc39d3b5127e405905ccfe28 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 30 Apr 2019 21:32:10 +0100 Subject: [PATCH 137/388] add coverage reporting --- .coveragerc | 14 ++++++++++++++ .gitignore | 2 ++ setup.cfg | 1 + 3 files changed, 17 insertions(+) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..5a292192 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,14 @@ +[run] +source = mock +omit = mock/tests/__main__.py + +[report] +exclude_lines = + pragma: no cover + if __name__ == .__main__.: + : pass + +[paths] +source = + mock/ + /root/project/mock/ diff --git a/.gitignore b/.gitignore index 70d2e1a9..b488ed92 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ runtox .testrepository .*.swp README.html +.coverage +.coverage.* diff --git a/setup.cfg b/setup.cfg index 39862cd4..677e2d14 100644 --- a/setup.cfg +++ b/setup.cfg @@ -37,6 +37,7 @@ docs = sphinx test = pytest + pytest-cov build = twine wheel From 9ed4bcd1538445c9ff780f18f77531b2a88a4e96 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 30 Apr 2019 21:16:04 +0100 Subject: [PATCH 138/388] Circle CI config --- .circleci/config.yml | 82 ++++++++++++++++++++++++++++++++++++++++++++ .travis.yml | 24 ------------- 2 files changed, 82 insertions(+), 24 deletions(-) create mode 100644 .circleci/config.yml delete mode 100644 .travis.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..2febe1c0 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,82 @@ +version: 2.1 + +orbs: + python: cjw296/python-ci@1.3 + +jobs: + docs: + docker: + - image: circleci/python:3.7 + steps: + - checkout + - run: + name: "Install Project" + command: "sudo pip install -e .[docs]" + - run: + name: "Build Docs" + command: | + python setup.py build_sphinx + rst2html.py --strict README.rst README.html + + +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 + - python/pip-run-tests: + name: python37 + image: circleci/python:3.7 + - python/pip-run-tests: + name: pypy27 + image: pypy:2.7 + sudo: false + - python/pip-run-tests: + name: pypy36 + image: pypy:3.6 + sudo: false + + - python/coverage: + name: coverage + requires: + - python27 + - python34 + - python35 + - python36 + - python37 + - pypy27 + - pypy36 + + - docs: + requires: + - coverage + + - python/release: + name: release + config: .carthorse.yml + requires: + - docs + filters: + branches: + only: master + +workflows: + push: + <<: *common + periodic: + <<: *common + triggers: + - schedule: + cron: "0 1 * * *" + filters: + branches: + only: master diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2764b966..00000000 --- a/.travis.yml +++ /dev/null @@ -1,24 +0,0 @@ -language: python -python: - - "2.7" - - "3.4" - - "3.5" - - "3.6" - - pypy - - pypy3 -matrix: - include: - - python: "3.7" - dist: xenial - - python: "nightly" - dist: xenial -install: - - pip install -U pip - - pip install -U wheel setuptools - - pip install -U .[docs,test] - - pip list - - python --version -script: - - pytest - - if [ -z "$SKIP_DOCS" ]; then python setup.py build_sphinx; fi - - rst2html.py --strict README.rst README.html From 700a51dff7e1966aa0eb29f5911eb8a6fb069d39 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 1 May 2019 23:08:56 +0100 Subject: [PATCH 139/388] carthorse config --- .carthorse.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .carthorse.yml diff --git a/.carthorse.yml b/.carthorse.yml new file mode 100644 index 00000000..28e55635 --- /dev/null +++ b/.carthorse.yml @@ -0,0 +1,10 @@ +carthorse: + version-from: setup.py + tag-format: "{version}" + when: + - version-not-tagged + actions: + - run: "sudo pip install -e .[build]" + - run: "sudo python setup.py sdist bdist_wheel" + - run: "twine upload -u carthorse-mock -p $PYPI_PASS dist/*" + - create-tag From dd7c43f9190b7fe5903a57e081b1b49dac0a912e Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 2 May 2019 07:04:19 +0100 Subject: [PATCH 140/388] remove a long-disabled test that wasn't present on cpython master --- mock/tests/testmock.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index cb1cc66c..5f6045af 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -1941,21 +1941,6 @@ def foo(a, b): pass self.assertRaises(TypeError, mock.child, 1) self.assertEqual(mock.mock_calls, [call.child(1, 2)]) - @unittest.expectedFailure - def test_pickle(self): - for Klass in (MagicMock, Mock, Subclass, NonCallableMagicMock): - mock = Klass(name='foo', attribute=3) - mock.foo(1, 2, 3) - data = pickle.dumps(mock) - new = pickle.loads(data) - - new.foo.assert_called_once_with(1, 2, 3) - self.assertFalse(new.called) - self.assertTrue(is_instance(new, Klass)) - self.assertIsInstance(new, Thing) - self.assertIn('name="foo"', repr(new)) - self.assertEqual(new.attribute, 3) - def test_isinstance_under_settrace(self): # bpo-36593 : __class__ is not set for a class that has __class__ # property defined when it's used with sys.settrace(trace) set. From d52cb782afe31f0e4736d1da2e173e6ff90492b8 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 2 May 2019 07:54:29 +0100 Subject: [PATCH 141/388] note about how to mark uncalled functions used in tests such that coverage ignores them. --- docs/index.txt | 14 ++++++++++++++ mock/tests/testcallable.py | 3 +-- mock/tests/testhelpers.py | 9 +++------ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/docs/index.txt b/docs/index.txt index d027ecf0..be078df6 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -104,6 +104,20 @@ Backporting rules 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 + + def will_never_be_called(): + pass + + 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(): pass + Backporting process ------------------- diff --git a/mock/tests/testcallable.py b/mock/tests/testcallable.py index 30e1f26b..729947e9 100644 --- a/mock/tests/testcallable.py +++ b/mock/tests/testcallable.py @@ -107,8 +107,7 @@ class Multi(SomeClass, Sub): pass class OldStyle: - def __call__(self): - pass + def __call__(self): pass class OldStyleSub(OldStyle): pass diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index 228c0c64..278fe6bb 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -614,8 +614,7 @@ class Baz(SomeClass, Bar): pass @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 + def f(self, a, b): pass class Bar(Foo): g = Foo() @@ -797,8 +796,7 @@ def __init__(self, a, b=3): pass @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 + def __init__(self, a, b=3): pass mock = create_autospec(Foo) @@ -904,8 +902,7 @@ def f(a, self): pass def test_autospec_property(self): class Foo(object): @property - def foo(self): - return 3 + def foo(self): pass foo = create_autospec(Foo) mock_property = foo.foo From 8d01e23bdf3345bbaad6c590686c8d561e6a8417 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 2 May 2019 07:57:24 +0100 Subject: [PATCH 142/388] Not used, see 637abcba241a69a1d9a8acf240855a6ed158cb64. --- mock/mock.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/mock/mock.py b/mock/mock.py index 27fe9ab3..37ea1ad6 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -362,9 +362,6 @@ def __getattr__(self, name): raise AttributeError return self._sentinels.setdefault(name, _SentinelObject(name)) - def __reduce__(self): - return 'sentinel' - sentinel = _Sentinel() From a8c4bfa29393f11ff37531787e089ca64a6f5c82 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 2 May 2019 08:05:49 +0100 Subject: [PATCH 143/388] test for python 2 _isidentifier --- mock/tests/testhelpers.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index 278fe6bb..d56a47f0 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -1025,6 +1025,14 @@ def test_spec_function_no_name(self): self.assertEqual(mock.__name__, 'funcopy') + @unittest.skipIf(six.PY3, "Here to test our Py2 _isidentifier") + def test_spec_function_has_identifier_name(self): + func = lambda: 'nope' + func.__name__ = 'global' + mock = create_autospec(func) + self.assertEqual(mock.__name__, 'funcopy') + + def test_spec_function_assert_has_calls(self): def f(a): pass mock = create_autospec(f) From a9c087d230e2a1b3de77322e010b4b92753b3b58 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 2 May 2019 08:17:51 +0100 Subject: [PATCH 144/388] New release instructions. --- docs/index.txt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/index.txt b/docs/index.txt index be078df6..68876e89 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -86,9 +86,16 @@ Releasing NB: please use semver. Bump the major component on API breaks, minor on all non-bugfix changes, patch on bugfix only changes. -1. tag -s, push --tags origin master -2. setup.py sdist bdist_wheel upload -s +1. Run ``release.py [major|minor|bugfix]`` which will roll out new + NEWS items, bump the version number and create a commit for the release. +2. Review that commit, feel free to amend it if you want to note anything + manually in ``CHANGELOG.rst``. + +3. Push to the ``master`` branch on + https://github.com/testing-cabal/mock.git and the Circle CI + automation will take care of pushing releases to PyPI and + creating a tag. Backporting rules ----------------- From d338bb04d38df493fd3830be70887fba6979ce9d Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 2 May 2019 08:26:57 +0100 Subject: [PATCH 145/388] tests for support code backported from Py3. --- mock/tests/testsupport.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 mock/tests/testsupport.py diff --git a/mock/tests/testsupport.py b/mock/tests/testsupport.py new file mode 100644 index 00000000..4882572b --- /dev/null +++ b/mock/tests/testsupport.py @@ -0,0 +1,14 @@ +# 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 From fd4c96631125858a3f67c9da7396c1dd7caebcd9 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 2 May 2019 08:32:29 +0100 Subject: [PATCH 146/388] the one uncovered line? --- mock/tests/testpatch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index 399961fb..958ea7fb 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -672,13 +672,13 @@ def test(): def test_patch_spec_set(self): - @patch('%s.SomeClass' % __name__, spec_set=SomeClass) + @patch('%s.SomeClass' % __name__, spec=SomeClass, spec_set=True) def test(MockClass): MockClass.z = 'foo' self.assertRaises(AttributeError, test) - @patch.object(support, 'SomeClass', spec_set=SomeClass) + @patch.object(support, 'SomeClass', spec=SomeClass, spec_set=True) def test(MockClass): MockClass.z = 'foo' From 337e3e19a140ccff0f79d9b5d8e6b5b8827da962 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 2 May 2019 08:50:00 +0100 Subject: [PATCH 147/388] new badges --- README.rst | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index a937b88d..8604f695 100644 --- a/README.rst +++ b/README.rst @@ -20,8 +20,13 @@ Please see the standard library documentation for more details. :Issue tracker: `GitHub Issues `_ :Build status: - .. image:: https://travis-ci.org/testing-cabal/mock.svg?branch=master - :target: https://travis-ci.org/testing-cabal/mock + |CircleCI|_ |Docs|_ + + .. |CircleCI| image:: https://circleci.com/gh/testing-cabal/mock/tree/master.svg?style=shield + .. _CircleCI: https://circleci.com/gh/testing-cabal/mock/tree/master + + .. |Docs| image:: https://readthedocs.org/projects/mock/badge/?version=latest + .. _Docs: http://mock.readthedocs.org/en/latest/ .. _Mock Homepage: https://github.com/testing-cabal/mock .. _BSD License: https://github.com/testing-cabal/mock/blob/master/LICENSE.txt From ed446fa21c4658ca17771b63550ef5442ef31e60 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 2 May 2019 08:53:59 +0100 Subject: [PATCH 148/388] fold pytest.ini into setup.cfg --- pytest.ini | 4 ---- setup.cfg | 5 +++++ 2 files changed, 5 insertions(+), 4 deletions(-) delete mode 100644 pytest.ini diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index af8b4498..00000000 --- a/pytest.ini +++ /dev/null @@ -1,4 +0,0 @@ -[pytest] -python_files=test*.py -filterwarnings = - ignore::DeprecationWarning diff --git a/setup.cfg b/setup.cfg index 677e2d14..b3e33a94 100644 --- a/setup.cfg +++ b/setup.cfg @@ -45,3 +45,8 @@ build = [bdist_wheel] universal = 1 + +[tool:pytest] +python_files=test*.py +filterwarnings = + ignore::DeprecationWarning From ac1cc209d0d0dade6dac23dd7e24de7ee6bda4b4 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 2 May 2019 09:02:53 +0100 Subject: [PATCH 149/388] Preparing for 3.0.0 release. --- CHANGELOG.rst | 77 +++++++++++++++++++ .../2017-10-17-12-04-37.bpo-30541.q3BM6C.rst | 2 - .../2018-04-30-22-43-31.bpo-32933.M3iI_y.rst | 2 - .../2018-05-15-17-06-42.bpo-33516.ZzARe4.rst | 1 - NEWS.d/2018-07-10-18-53-46.bpo-0.UBQJBc.rst | 1 - .../2018-09-14-10-38-18.bpo-31177.Sv91TN.rst | 2 - .../2018-10-18-17-57-28.bpo-35022.KeEF4T.rst | 2 - .../2018-10-25-09-59-00.bpo-35047.abbaa.rst | 3 - .../2018-10-27-11-54-12.bpo-35082.HDj1nr.rst | 2 - .../2018-11-15-07-14-32.bpo-35226.wJPEEe.rst | 3 - .../2018-12-03-21-20-24.bpo-35357.rhhoiC.rst | 4 - .../2018-12-06-00-43-13.bpo-35330.abB4BN.rst | 4 - .../2018-12-09-17-04-15.bpo-17185.SfSCJF.rst | 2 - .../2018-12-09-21-35-49.bpo-20239.V4mWBL.rst | 2 - .../2018-12-10-13-18-37.bpo-26704.DBAN4c.rst | 2 - .../2018-12-21-09-54-30.bpo-21478.5gsXtc.rst | 2 - .../2019-01-18-23-10-10.bpo-23078.l4dFoj.rst | 2 - .../2019-02-10-00-00-13.bpo-35500.1HOMmo.rst | 1 - .../2019-02-10-16-49-16.bpo-21269.Fqi7VH.rst | 1 - .../2019-02-24-00-04-10.bpo-35512.eWDjCJ.rst | 3 - .../2019-03-20-15-13-18.bpo-36366.n0eav_.rst | 4 - .../2019-04-11-22-11-24.bpo-36598.hfzDUl.rst | 2 - mock/mock.py | 2 +- 23 files changed, 78 insertions(+), 48 deletions(-) delete mode 100644 NEWS.d/2017-10-17-12-04-37.bpo-30541.q3BM6C.rst delete mode 100644 NEWS.d/2018-04-30-22-43-31.bpo-32933.M3iI_y.rst delete mode 100644 NEWS.d/2018-05-15-17-06-42.bpo-33516.ZzARe4.rst delete mode 100644 NEWS.d/2018-07-10-18-53-46.bpo-0.UBQJBc.rst delete mode 100644 NEWS.d/2018-09-14-10-38-18.bpo-31177.Sv91TN.rst delete mode 100644 NEWS.d/2018-10-18-17-57-28.bpo-35022.KeEF4T.rst delete mode 100644 NEWS.d/2018-10-25-09-59-00.bpo-35047.abbaa.rst delete mode 100644 NEWS.d/2018-10-27-11-54-12.bpo-35082.HDj1nr.rst delete mode 100644 NEWS.d/2018-11-15-07-14-32.bpo-35226.wJPEEe.rst delete mode 100644 NEWS.d/2018-12-03-21-20-24.bpo-35357.rhhoiC.rst delete mode 100644 NEWS.d/2018-12-06-00-43-13.bpo-35330.abB4BN.rst delete mode 100644 NEWS.d/2018-12-09-17-04-15.bpo-17185.SfSCJF.rst delete mode 100644 NEWS.d/2018-12-09-21-35-49.bpo-20239.V4mWBL.rst delete mode 100644 NEWS.d/2018-12-10-13-18-37.bpo-26704.DBAN4c.rst delete mode 100644 NEWS.d/2018-12-21-09-54-30.bpo-21478.5gsXtc.rst delete mode 100644 NEWS.d/2019-01-18-23-10-10.bpo-23078.l4dFoj.rst delete mode 100644 NEWS.d/2019-02-10-00-00-13.bpo-35500.1HOMmo.rst delete mode 100644 NEWS.d/2019-02-10-16-49-16.bpo-21269.Fqi7VH.rst delete mode 100644 NEWS.d/2019-02-24-00-04-10.bpo-35512.eWDjCJ.rst delete mode 100644 NEWS.d/2019-03-20-15-13-18.bpo-36366.n0eav_.rst delete mode 100644 NEWS.d/2019-04-11-22-11-24.bpo-36598.hfzDUl.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8716d702..e2fb72b1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,80 @@ +3.0.0 +----- + +- Issue #35226: Recursively check arguments when testing for equality of + :class:`unittest.mock.call` objects and add note that tracking of + parameters used to create ancestors of mocks in ``mock_calls`` is not + possible. + +- Issue #31177: Fix bug that prevented using :meth:`reset_mock + ` on mock instances with deleted attributes + +- Issue #26704: Added test demonstrating double-patching of an instance + method. Patch by Anthony Sottile. + +- Issue #35500: Write expected and actual call parameters on separate lines + in :meth:`unittest.mock.Mock.assert_called_with` assertion errors. + Contributed by Susan Su. + +- Issue #35330: When a :class:`Mock` instance was used to wrap an object, if + `side_effect` is used in one of the mocks of it methods, don't call the + original implementation and return the result of using the side effect the + same way that it is done with return_value. + +- Issue #30541: Add new function to seal a mock and prevent the + automatically creation of child mocks. Patch by Mario Corchero. + +- Issue #35022: :class:`unittest.mock.MagicMock` now supports the + ``__fspath__`` method (from :class:`os.PathLike`). + +- Issue #33516: :class:`unittest.mock.MagicMock` now supports the + ``__round__`` magic method. + +- Issue #35512: :func:`unittest.mock.patch.dict` used as a decorator with + string target resolves the target during function call instead of during + decorator construction. Patch by Karthikeyan Singaravelan. + +- Issue #36366: Calling ``stop()`` on an unstarted or stopped + :func:`unittest.mock.patch` object will now return `None` instead of + raising :exc:`RuntimeError`, making the method idempotent. Patch + byKarthikeyan Singaravelan. + +- Issue #35357: Internal attributes' names of unittest.mock._Call and + unittest.mock.MagicProxy (name, parent & from_kall) are now prefixed with + _mock_ in order to prevent clashes with widely used object attributes. + Fixed minor typo in test function name. + +- Issue #20239: Allow repeated assignment deletion of + :class:`unittest.mock.Mock` attributes. Patch by Pablo Galindo. + +- Issue #35082: Don't return deleted attributes when calling dir on a + :class:`unittest.mock.Mock`. + +- Issue #0: Improved an error message when mock assert_has_calls fails. + +- Issue #23078: Add support for :func:`classmethod` and :func:`staticmethod` + to :func:`unittest.mock.create_autospec`. Initial patch by Felipe Ochoa. + +- Issue #21478: Calls to a child function created with + :func:`unittest.mock.create_autospec` should propagate to the parent. + Patch by Karthikeyan Singaravelan. + +- Issue #36598: Fix ``isinstance`` check for Mock objects with spec when the + code is executed under tracing. Patch by Karthikeyan Singaravelan. + +- Issue #32933: :func:`unittest.mock.mock_open` now supports iteration over + the file contents. Patch by Tony Flury. + +- Issue #21269: Add ``args`` and ``kwargs`` properties to mock call objects. + Contributed by Kumar Akshay. + +- Issue #17185: Set ``__signature__`` on mock for :mod:`inspect` to get + signature. Patch by Karthikeyan Singaravelan. + +- Issue #35047: ``unittest.mock`` now includes mock calls in exception + messages if ``assert_not_called``, ``assert_called_once``, or + ``assert_called_once_with`` fails. Patch by Petter Strandmark. + - Issue #28380: unittest.mock Mock autospec functions now properly support assert_called, assert_not_called, and assert_called_once. diff --git a/NEWS.d/2017-10-17-12-04-37.bpo-30541.q3BM6C.rst b/NEWS.d/2017-10-17-12-04-37.bpo-30541.q3BM6C.rst deleted file mode 100644 index 7eb5e16f..00000000 --- a/NEWS.d/2017-10-17-12-04-37.bpo-30541.q3BM6C.rst +++ /dev/null @@ -1,2 +0,0 @@ -Add new function to seal a mock and prevent the automatically creation of -child mocks. Patch by Mario Corchero. diff --git a/NEWS.d/2018-04-30-22-43-31.bpo-32933.M3iI_y.rst b/NEWS.d/2018-04-30-22-43-31.bpo-32933.M3iI_y.rst deleted file mode 100644 index 4de7a8f9..00000000 --- a/NEWS.d/2018-04-30-22-43-31.bpo-32933.M3iI_y.rst +++ /dev/null @@ -1,2 +0,0 @@ -:func:`unittest.mock.mock_open` now supports iteration over the file -contents. Patch by Tony Flury. diff --git a/NEWS.d/2018-05-15-17-06-42.bpo-33516.ZzARe4.rst b/NEWS.d/2018-05-15-17-06-42.bpo-33516.ZzARe4.rst deleted file mode 100644 index 77b1428f..00000000 --- a/NEWS.d/2018-05-15-17-06-42.bpo-33516.ZzARe4.rst +++ /dev/null @@ -1 +0,0 @@ -:class:`unittest.mock.MagicMock` now supports the ``__round__`` magic method. diff --git a/NEWS.d/2018-07-10-18-53-46.bpo-0.UBQJBc.rst b/NEWS.d/2018-07-10-18-53-46.bpo-0.UBQJBc.rst deleted file mode 100644 index 9d826776..00000000 --- a/NEWS.d/2018-07-10-18-53-46.bpo-0.UBQJBc.rst +++ /dev/null @@ -1 +0,0 @@ -Improved an error message when mock assert_has_calls fails. diff --git a/NEWS.d/2018-09-14-10-38-18.bpo-31177.Sv91TN.rst b/NEWS.d/2018-09-14-10-38-18.bpo-31177.Sv91TN.rst deleted file mode 100644 index f385571e..00000000 --- a/NEWS.d/2018-09-14-10-38-18.bpo-31177.Sv91TN.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix bug that prevented using :meth:`reset_mock ` -on mock instances with deleted attributes diff --git a/NEWS.d/2018-10-18-17-57-28.bpo-35022.KeEF4T.rst b/NEWS.d/2018-10-18-17-57-28.bpo-35022.KeEF4T.rst deleted file mode 100644 index 426be70c..00000000 --- a/NEWS.d/2018-10-18-17-57-28.bpo-35022.KeEF4T.rst +++ /dev/null @@ -1,2 +0,0 @@ -:class:`unittest.mock.MagicMock` now supports the ``__fspath__`` method -(from :class:`os.PathLike`). diff --git a/NEWS.d/2018-10-25-09-59-00.bpo-35047.abbaa.rst b/NEWS.d/2018-10-25-09-59-00.bpo-35047.abbaa.rst deleted file mode 100644 index 12eda275..00000000 --- a/NEWS.d/2018-10-25-09-59-00.bpo-35047.abbaa.rst +++ /dev/null @@ -1,3 +0,0 @@ -``unittest.mock`` now includes mock calls in exception messages if -``assert_not_called``, ``assert_called_once``, or ``assert_called_once_with`` -fails. Patch by Petter Strandmark. \ No newline at end of file diff --git a/NEWS.d/2018-10-27-11-54-12.bpo-35082.HDj1nr.rst b/NEWS.d/2018-10-27-11-54-12.bpo-35082.HDj1nr.rst deleted file mode 100644 index 45a07295..00000000 --- a/NEWS.d/2018-10-27-11-54-12.bpo-35082.HDj1nr.rst +++ /dev/null @@ -1,2 +0,0 @@ -Don't return deleted attributes when calling dir on a -:class:`unittest.mock.Mock`. diff --git a/NEWS.d/2018-11-15-07-14-32.bpo-35226.wJPEEe.rst b/NEWS.d/2018-11-15-07-14-32.bpo-35226.wJPEEe.rst deleted file mode 100644 index b95cc979..00000000 --- a/NEWS.d/2018-11-15-07-14-32.bpo-35226.wJPEEe.rst +++ /dev/null @@ -1,3 +0,0 @@ -Recursively check arguments when testing for equality of -:class:`unittest.mock.call` objects and add note that tracking of parameters -used to create ancestors of mocks in ``mock_calls`` is not possible. diff --git a/NEWS.d/2018-12-03-21-20-24.bpo-35357.rhhoiC.rst b/NEWS.d/2018-12-03-21-20-24.bpo-35357.rhhoiC.rst deleted file mode 100644 index 1dade5ba..00000000 --- a/NEWS.d/2018-12-03-21-20-24.bpo-35357.rhhoiC.rst +++ /dev/null @@ -1,4 +0,0 @@ -Internal attributes' names of unittest.mock._Call and -unittest.mock.MagicProxy (name, parent & from_kall) are now prefixed with -_mock_ in order to prevent clashes with widely used object attributes. -Fixed minor typo in test function name. diff --git a/NEWS.d/2018-12-06-00-43-13.bpo-35330.abB4BN.rst b/NEWS.d/2018-12-06-00-43-13.bpo-35330.abB4BN.rst deleted file mode 100644 index 24d0ab84..00000000 --- a/NEWS.d/2018-12-06-00-43-13.bpo-35330.abB4BN.rst +++ /dev/null @@ -1,4 +0,0 @@ -When a :class:`Mock` instance was used to wrap an object, if `side_effect` -is used in one of the mocks of it methods, don't call the original -implementation and return the result of using the side effect the same way -that it is done with return_value. diff --git a/NEWS.d/2018-12-09-17-04-15.bpo-17185.SfSCJF.rst b/NEWS.d/2018-12-09-17-04-15.bpo-17185.SfSCJF.rst deleted file mode 100644 index 311c6d2b..00000000 --- a/NEWS.d/2018-12-09-17-04-15.bpo-17185.SfSCJF.rst +++ /dev/null @@ -1,2 +0,0 @@ -Set ``__signature__`` on mock for :mod:`inspect` to get signature. -Patch by Karthikeyan Singaravelan. diff --git a/NEWS.d/2018-12-09-21-35-49.bpo-20239.V4mWBL.rst b/NEWS.d/2018-12-09-21-35-49.bpo-20239.V4mWBL.rst deleted file mode 100644 index fe9c69d2..00000000 --- a/NEWS.d/2018-12-09-21-35-49.bpo-20239.V4mWBL.rst +++ /dev/null @@ -1,2 +0,0 @@ -Allow repeated assignment deletion of :class:`unittest.mock.Mock` attributes. -Patch by Pablo Galindo. diff --git a/NEWS.d/2018-12-10-13-18-37.bpo-26704.DBAN4c.rst b/NEWS.d/2018-12-10-13-18-37.bpo-26704.DBAN4c.rst deleted file mode 100644 index 458f495b..00000000 --- a/NEWS.d/2018-12-10-13-18-37.bpo-26704.DBAN4c.rst +++ /dev/null @@ -1,2 +0,0 @@ -Added test demonstrating double-patching of an instance method. Patch by -Anthony Sottile. diff --git a/NEWS.d/2018-12-21-09-54-30.bpo-21478.5gsXtc.rst b/NEWS.d/2018-12-21-09-54-30.bpo-21478.5gsXtc.rst deleted file mode 100644 index 1000748c..00000000 --- a/NEWS.d/2018-12-21-09-54-30.bpo-21478.5gsXtc.rst +++ /dev/null @@ -1,2 +0,0 @@ -Calls to a child function created with :func:`unittest.mock.create_autospec` -should propagate to the parent. Patch by Karthikeyan Singaravelan. diff --git a/NEWS.d/2019-01-18-23-10-10.bpo-23078.l4dFoj.rst b/NEWS.d/2019-01-18-23-10-10.bpo-23078.l4dFoj.rst deleted file mode 100644 index 975cc9c0..00000000 --- a/NEWS.d/2019-01-18-23-10-10.bpo-23078.l4dFoj.rst +++ /dev/null @@ -1,2 +0,0 @@ -Add support for :func:`classmethod` and :func:`staticmethod` to -:func:`unittest.mock.create_autospec`. Initial patch by Felipe Ochoa. diff --git a/NEWS.d/2019-02-10-00-00-13.bpo-35500.1HOMmo.rst b/NEWS.d/2019-02-10-00-00-13.bpo-35500.1HOMmo.rst deleted file mode 100644 index 16b0fbf7..00000000 --- a/NEWS.d/2019-02-10-00-00-13.bpo-35500.1HOMmo.rst +++ /dev/null @@ -1 +0,0 @@ -Write expected and actual call parameters on separate lines in :meth:`unittest.mock.Mock.assert_called_with` assertion errors. Contributed by Susan Su. diff --git a/NEWS.d/2019-02-10-16-49-16.bpo-21269.Fqi7VH.rst b/NEWS.d/2019-02-10-16-49-16.bpo-21269.Fqi7VH.rst deleted file mode 100644 index 15ad636a..00000000 --- a/NEWS.d/2019-02-10-16-49-16.bpo-21269.Fqi7VH.rst +++ /dev/null @@ -1 +0,0 @@ -Add ``args`` and ``kwargs`` properties to mock call objects. Contributed by Kumar Akshay. diff --git a/NEWS.d/2019-02-24-00-04-10.bpo-35512.eWDjCJ.rst b/NEWS.d/2019-02-24-00-04-10.bpo-35512.eWDjCJ.rst deleted file mode 100644 index 8281b1b2..00000000 --- a/NEWS.d/2019-02-24-00-04-10.bpo-35512.eWDjCJ.rst +++ /dev/null @@ -1,3 +0,0 @@ -:func:`unittest.mock.patch.dict` used as a decorator with string target -resolves the target during function call instead of during decorator -construction. Patch by Karthikeyan Singaravelan. diff --git a/NEWS.d/2019-03-20-15-13-18.bpo-36366.n0eav_.rst b/NEWS.d/2019-03-20-15-13-18.bpo-36366.n0eav_.rst deleted file mode 100644 index a4350483..00000000 --- a/NEWS.d/2019-03-20-15-13-18.bpo-36366.n0eav_.rst +++ /dev/null @@ -1,4 +0,0 @@ -Calling ``stop()`` on an unstarted or stopped :func:`unittest.mock.patch` -object will now return `None` instead of raising :exc:`RuntimeError`, -making the method idempotent. -Patch byKarthikeyan Singaravelan. diff --git a/NEWS.d/2019-04-11-22-11-24.bpo-36598.hfzDUl.rst b/NEWS.d/2019-04-11-22-11-24.bpo-36598.hfzDUl.rst deleted file mode 100644 index 2a798020..00000000 --- a/NEWS.d/2019-04-11-22-11-24.bpo-36598.hfzDUl.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix ``isinstance`` check for Mock objects with spec when the code is -executed under tracing. Patch by Karthikeyan Singaravelan. diff --git a/mock/mock.py b/mock/mock.py index 37ea1ad6..12ec7bf7 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -68,7 +68,7 @@ import six from six import wraps -__version__ = '2.0.0' +__version__ = '3.0.0' version_info = tuple(int(p) for p in __version__.split('.')) import mock From 7f848cb9050bdc2b1b3b8498249544407124b03d Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 2 May 2019 10:05:33 +0100 Subject: [PATCH 150/388] add missing install requirement of six --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index b3e33a94..57c62a17 100644 --- a/setup.cfg +++ b/setup.cfg @@ -29,6 +29,7 @@ keyword = testing, test, mock, mocking, unittest, patching, stubs, fakes, doubles [options] +install_requires=six python_requires=>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* packages = mock From de299e625fa225c8a638ef35993e8b8989244a45 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 2 May 2019 10:05:39 +0100 Subject: [PATCH 151/388] Preparing for 3.0.1 release. --- CHANGELOG.rst | 5 +++++ mock/mock.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e2fb72b1..1672271b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,8 @@ +3.0.1 +----- + +- Fix packaging issue where ``six`` was missed as a dependency. + 3.0.0 ----- diff --git a/mock/mock.py b/mock/mock.py index 12ec7bf7..3011662e 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -68,7 +68,7 @@ import six from six import wraps -__version__ = '3.0.0' +__version__ = '3.0.1' version_info = tuple(int(p) for p in __version__.split('.')) import mock From 1916b1d7db02e2e2f6b9b51930ad72a8d4649c8b Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 2 May 2019 10:11:48 +0100 Subject: [PATCH 152/388] flip RTD to be the main landing page --- README.rst | 4 +++- setup.cfg | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 8604f695..b4f3163c 100644 --- a/README.rst +++ b/README.rst @@ -17,6 +17,8 @@ Please see the standard library documentation for more details. :License: `BSD License`_ :Support: `Mailing list (testing-in-python@lists.idyll.org) `_ +:Code: `GitHub + `_ :Issue tracker: `GitHub Issues `_ :Build status: @@ -28,7 +30,7 @@ Please see the standard library documentation for more details. .. |Docs| image:: https://readthedocs.org/projects/mock/badge/?version=latest .. _Docs: http://mock.readthedocs.org/en/latest/ -.. _Mock Homepage: https://github.com/testing-cabal/mock +.. _Mock Homepage: http://mock.readthedocs.org/en/latest/ .. _BSD License: https://github.com/testing-cabal/mock/blob/master/LICENSE.txt .. _Python Docs: https://docs.python.org/dev/library/unittest.mock.html .. _mock on PyPI: https://pypi.org/project/mock/ diff --git a/setup.cfg b/setup.cfg index 57c62a17..66d0fb24 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = mock summary = Rolling backport of unittest.mock for all Pythons -home-page = https://github.com/testing-cabal/mock +home-page = http://mock.readthedocs.org/en/latest/ description-file = README.rst author = Testing Cabal author-email = testing-in-python@lists.idyll.org From 9c75ce975c2fb21be64cc3f5353060f5f61c4ef3 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 2 May 2019 10:14:10 +0100 Subject: [PATCH 153/388] - Add missing ``funcsigs`` dependency on Python 2. --- CHANGELOG.rst | 3 +++ setup.cfg | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1672271b..a42de9eb 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,6 @@ + +- Add missing ``funcsigs`` dependency on Python 2. + 3.0.1 ----- diff --git a/setup.cfg b/setup.cfg index 66d0fb24..7283b793 100644 --- a/setup.cfg +++ b/setup.cfg @@ -29,7 +29,9 @@ keyword = testing, test, mock, mocking, unittest, patching, stubs, fakes, doubles [options] -install_requires=six +install_requires = + six + funcsigs>=1;python_version<"3.3" python_requires=>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* packages = mock From 67ca2505fccae09e6e1cd1406dff4cadfb119e43 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 2 May 2019 10:14:18 +0100 Subject: [PATCH 154/388] Preparing for 3.0.2 release. --- CHANGELOG.rst | 4 ++++ mock/mock.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a42de9eb..575c975b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,7 @@ +3.0.2 +----- + + - Add missing ``funcsigs`` dependency on Python 2. diff --git a/mock/mock.py b/mock/mock.py index 3011662e..7f3bafe2 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -68,7 +68,7 @@ import six from six import wraps -__version__ = '3.0.1' +__version__ = '3.0.2' version_info = tuple(int(p) for p in __version__.split('.')) import mock From 536ffbb4a37054e2270d4ae56edb8e651f2c55b1 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 2 May 2019 10:16:14 +0100 Subject: [PATCH 155/388] trim whitespace --- CHANGELOG.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 575c975b..4c3a8b6b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,8 +1,6 @@ 3.0.2 ----- - - - Add missing ``funcsigs`` dependency on Python 2. 3.0.1 From b66b5b9d75b19b4b8cb11beec9d0da0aab43a764 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 2 May 2019 13:21:18 +0100 Subject: [PATCH 156/388] Use basestring when checking for dictionary patching. Fixes https://github.com/testing-cabal/mock/issues/458. --- mock/mock.py | 2 +- mock/tests/testpatch.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/mock/mock.py b/mock/mock.py index 7f3bafe2..5d4b420e 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1768,7 +1768,7 @@ def __enter__(self): def _patch_dict(self): values = self.values - if isinstance(self.in_dict, str): + if isinstance(self.in_dict, basestring): self.in_dict = _importer(self.in_dict) in_dict = self.in_dict clear = self.clear diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index 958ea7fb..bbd6d26d 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -656,6 +656,14 @@ 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 From 66381c07b8cef912cd8732a87c0f1b9293230566 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 2 May 2019 13:24:39 +0100 Subject: [PATCH 157/388] Note about changelog entries for changes not in cpython. --- docs/changelog.txt | 4 ++-- docs/index.txt | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 03e051e5..4de03af3 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -1,4 +1,4 @@ -Changelog from Python's News -============================ +Changelog +========= .. include:: ../CHANGELOG.rst diff --git a/docs/index.txt b/docs/index.txt index 68876e89..4e8bc17d 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -59,8 +59,8 @@ with Mock functionality should be reported to the `Python bug tracker .. index:: python changes -Python Changes -++++++++++++++ +Changelog ++++++++++ See the :doc:`change log `. @@ -78,7 +78,11 @@ 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, nightly Python 3 builds, pypy, pypy3. +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. Releasing --------- From 1efb62fcac21617933448603b2eb92ea9511063a Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 2 May 2019 13:25:52 +0100 Subject: [PATCH 158/388] Preparing for 3.0.3 release. --- CHANGELOG.rst | 5 +++++ mock/mock.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4c3a8b6b..45728b01 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,8 @@ +3.0.3 +----- + +- Fixed patching of dictionaries with a Unicodes on Python 2. + 3.0.2 ----- diff --git a/mock/mock.py b/mock/mock.py index 5d4b420e..138b3206 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -68,7 +68,7 @@ import six from six import wraps -__version__ = '3.0.2' +__version__ = '3.0.3' version_info = tuple(int(p) for p in __version__.split('.')) import mock From 2bab5851ec99fedce28f641108776fe7ea709e8d Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 2 May 2019 13:30:58 +0100 Subject: [PATCH 159/388] Fix up clumsy changelog entry. --- CHANGELOG.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 45728b01..0699690d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,7 +1,8 @@ 3.0.3 ----- -- Fixed patching of dictionaries with a Unicodes on Python 2. +- Fixed patching of dictionaries, when specifing the target with a + unicode on Python 2. 3.0.2 ----- From 8068fe70f0c1b16eccb1c3f6ec2beacc5989138e Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 2 May 2019 14:30:59 +0100 Subject: [PATCH 160/388] need more sleep. --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0699690d..4e6605e7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,7 +1,7 @@ 3.0.3 ----- -- Fixed patching of dictionaries, when specifing the target with a +- Fixed patching of dictionaries, when specifying the target with a unicode on Python 2. 3.0.2 From eec632991c06b9458fc8e0f84bf4042d1a5cc1af Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Fri, 3 May 2019 07:12:43 +0100 Subject: [PATCH 161/388] Change packaging and CI to make sure packages are built correctly. --- .carthorse.yml | 1 - .circleci/config.yml | 72 +++++++++++++++++++++++++++++++++----------- 2 files changed, 54 insertions(+), 19 deletions(-) diff --git a/.carthorse.yml b/.carthorse.yml index 28e55635..d8f3a9a0 100644 --- a/.carthorse.yml +++ b/.carthorse.yml @@ -5,6 +5,5 @@ carthorse: - version-not-tagged actions: - run: "sudo pip install -e .[build]" - - run: "sudo python setup.py sdist bdist_wheel" - run: "twine upload -u carthorse-mock -p $PYPI_PASS dist/*" - create-tag diff --git a/.circleci/config.yml b/.circleci/config.yml index 2febe1c0..8416914f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,22 +1,25 @@ version: 2.1 orbs: - python: cjw296/python-ci@1.3 + python: cjw296/python-ci@2 jobs: - docs: + check-package: + parameters: + image: + type: string + python: + type: string + default: "python" docker: - - image: circleci/python:3.7 + - image: << parameters.image >> steps: - - checkout - - run: - name: "Install Project" - command: "sudo pip install -e .[docs]" - - run: - name: "Build Docs" - command: | - python setup.py build_sphinx - rst2html.py --strict README.rst README.html + - python/check-package: + package: "mock" + test: + - run: + name: "Import package" + command: << parameters.python >> -c "import mock" common: &common @@ -39,11 +42,9 @@ common: &common - python/pip-run-tests: name: pypy27 image: pypy:2.7 - sudo: false - python/pip-run-tests: name: pypy36 image: pypy:3.6 - sudo: false - python/coverage: name: coverage @@ -56,19 +57,54 @@ common: &common - pypy27 - pypy36 - - docs: + - python/pip-docs: + name: docs requires: - coverage - - python/release: - name: release - config: .carthorse.yml + - python/pip-setuptools-build-package: + name: package requires: - docs filters: branches: only: master + - check-package: + name: check-package-python27 + image: circleci/python:2.7 + requires: + - package + + - check-package: + name: check-package-python37 + image: circleci/python:3.7 + requires: + - package + + - check-package: + name: check-package-pypy27 + image: pypy:2.7 + python: pypy + requires: + - package + + - check-package: + name: check-package-pypy36 + image: pypy:3.6 + python: pypy3 + requires: + - package + + - python/release: + name: release + config: .carthorse.yml + requires: + - check-package-python27 + - check-package-python37 + - check-package-pypy27 + - check-package-pypy36 + workflows: push: <<: *common From e1896ffa8f0afdd42fafdf654f5221036614f3d0 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Fri, 3 May 2019 07:28:49 +0100 Subject: [PATCH 162/388] include the license, readme and changelog in sdist --- MANIFEST.in | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..7f47ab66 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include LICENSE.txt +include *.rst From 74f6a7e7c7fd13bbc78c1f3a3f582478910872d7 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sat, 4 May 2019 13:55:53 +0100 Subject: [PATCH 163/388] Preparing for 3.0.4 release. --- CHANGELOG.rst | 5 +++++ mock/mock.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4e6605e7..f696a853 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,8 @@ +3.0.4 +----- + +- Include the license, readme and changelog in the source distribution. + 3.0.3 ----- diff --git a/mock/mock.py b/mock/mock.py index 138b3206..19cb4dda 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -68,7 +68,7 @@ import six from six import wraps -__version__ = '3.0.3' +__version__ = '3.0.4' version_info = tuple(int(p) for p in __version__.split('.')) import mock From 4bd71febaa93ebdb4ef8d7903c4e018a70da345a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Tue, 7 May 2019 12:48:36 +0200 Subject: [PATCH 164/388] bpo-31855: unittest.mock.mock_open() results now respects the argument of read([size]) (GH-11521) unittest.mock.mock_open() results now respects the argument of read([size]) Co-Authored-By: remilapeyre Backports: 11a8832c98b3db78727312154dd1d3ba76d639ec Signed-off-by: Chris Withers --- .../2019-01-11-17-09-15.bpo-31855.PlhfsX.rst | 2 + mock/mock.py | 38 +++++++------------ mock/tests/testwith.py | 7 +++- 3 files changed, 22 insertions(+), 25 deletions(-) create mode 100644 NEWS.d/2019-01-11-17-09-15.bpo-31855.PlhfsX.rst diff --git a/NEWS.d/2019-01-11-17-09-15.bpo-31855.PlhfsX.rst b/NEWS.d/2019-01-11-17-09-15.bpo-31855.PlhfsX.rst new file mode 100644 index 00000000..0da9c499 --- /dev/null +++ b/NEWS.d/2019-01-11-17-09-15.bpo-31855.PlhfsX.rst @@ -0,0 +1,2 @@ +:func:`unittest.mock.mock_open` results now respects the argument of read([size]). +Patch contributed by Rémi Lapeyre. diff --git a/mock/mock.py b/mock/mock.py index 19cb4dda..2d4f8058 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -55,6 +55,7 @@ from functools import partial +import io import inspect import pprint import sys @@ -2493,25 +2494,13 @@ def __init__(self, spec, spec_set=False, parent=None, file_spec = None -def _iterate_read_data(read_data): - # Helper for mock_open: - # Retrieve lines from read_data via a generator so that separate calls to - # readline, read, and readlines are properly interleaved - sep = b'\n' if isinstance(read_data, bytes) else '\n' - data_as_list = [l + sep for l in read_data.split(sep)] - - if data_as_list[-1] == sep: - # If the last line ended in a newline, the list comprehension will have an - # extra entry that's just a newline. Remove this. - data_as_list = data_as_list[:-1] + +def _to_stream(read_data): + if isinstance(read_data, bytes): + return io.BytesIO(read_data) else: - # If there wasn't an extra newline by itself, then the file being - # emulated doesn't have a newline to end the last line remove the - # newline that our naive format() added - data_as_list[-1] = data_as_list[-1][:-1] + return io.StringIO(read_data) - for line in data_as_list: - yield line def mock_open(mock=None, read_data=''): """ @@ -2525,21 +2514,24 @@ def mock_open(mock=None, read_data=''): `read_data` is a string for the `read`, `readline` and `readlines` of the file handle to return. This is an empty string by default. """ + _read_data = _to_stream(read_data) + _state = [_read_data, None] + def _readlines_side_effect(*args, **kwargs): if handle.readlines.return_value is not None: return handle.readlines.return_value - return list(_state[0]) + return _state[0].readlines(*args, **kwargs) def _read_side_effect(*args, **kwargs): if handle.read.return_value is not None: return handle.read.return_value - return type(read_data)().join(_state[0]) + return _state[0].read(*args, **kwargs) - def _readline_side_effect(): + def _readline_side_effect(*args, **kwargs): for item in _iter_side_effect(): yield item while True: - yield type(read_data)() + yield _state[0].readline(*args, **kwargs) def _iter_side_effect(): if handle.readline.return_value is not None: @@ -2563,8 +2555,6 @@ def _iter_side_effect(): handle = MagicMock(spec=file_spec) handle.__enter__.return_value = handle - _state = [_iterate_read_data(read_data), None] - handle.write.return_value = None handle.read.return_value = None handle.readline.return_value = None @@ -2577,7 +2567,7 @@ def _iter_side_effect(): handle.__iter__.side_effect = _iter_side_effect def reset_data(*args, **kwargs): - _state[0] = _iterate_read_data(read_data) + _state[0] = _to_stream(read_data) if handle.readline.side_effect == _state[1]: # Only reset the side effect if the user hasn't overridden it. _state[1] = _readline_side_effect() diff --git a/mock/tests/testwith.py b/mock/tests/testwith.py index 31e8322b..587fde9c 100644 --- a/mock/tests/testwith.py +++ b/mock/tests/testwith.py @@ -288,7 +288,12 @@ def test_mock_open_read_with_argument(self): # for mocks returned by mock_open some_data = 'foo\nbar\nbaz' mock = mock_open(read_data=some_data) - self.assertEqual(mock().read(10), some_data) + self.assertEqual(mock().read(10), some_data[:10]) + self.assertEqual(mock().read(10), some_data[:10]) + + f = mock() + self.assertEqual(f.read(10), some_data[:10]) + self.assertEqual(f.read(10), some_data[10:]) def test_interleaved_reads(self): From e8891607ee6917784b802e905c4aafcc8cdc99e8 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 7 May 2019 22:08:14 +0100 Subject: [PATCH 165/388] latest sync point --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index 8715e2e8..1f18392f 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -adbf178e49113b2de0042e86a1228560475a65c5 +11a8832c98b3db78727312154dd1d3ba76d639ec From e0180b98d0e07e895a3f699b7e9afcac4716fc03 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 7 May 2019 22:17:29 +0100 Subject: [PATCH 166/388] Preparing for 3.0.5 release. --- CHANGELOG.rst | 6 ++++++ NEWS.d/2019-01-11-17-09-15.bpo-31855.PlhfsX.rst | 2 -- mock/mock.py | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) delete mode 100644 NEWS.d/2019-01-11-17-09-15.bpo-31855.PlhfsX.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f696a853..919648bc 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,9 @@ +3.0.5 +----- + +- Issue #31855: :func:`unittest.mock.mock_open` results now respects the + argument of read([size]). Patch contributed by Rémi Lapeyre. + 3.0.4 ----- diff --git a/NEWS.d/2019-01-11-17-09-15.bpo-31855.PlhfsX.rst b/NEWS.d/2019-01-11-17-09-15.bpo-31855.PlhfsX.rst deleted file mode 100644 index 0da9c499..00000000 --- a/NEWS.d/2019-01-11-17-09-15.bpo-31855.PlhfsX.rst +++ /dev/null @@ -1,2 +0,0 @@ -:func:`unittest.mock.mock_open` results now respects the argument of read([size]). -Patch contributed by Rémi Lapeyre. diff --git a/mock/mock.py b/mock/mock.py index 2d4f8058..2d39253e 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -69,7 +69,7 @@ import six from six import wraps -__version__ = '3.0.4' +__version__ = '3.0.5' version_info = tuple(int(p) for p in __version__.split('.')) import mock From 8860004e2faa16434ede4e36c9314d12dd5a3eca Mon Sep 17 00:00:00 2001 From: Kurt Mosiejczuk Date: Mon, 16 Sep 2019 17:15:28 -0400 Subject: [PATCH 167/388] Include regression tests via MANIFEST.in so tests will be in PyPI tarball --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 7f47ab66..27027db1 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,3 @@ include LICENSE.txt include *.rst +recursive-include mock/tests *.py From 8dd03de6ace6fed7f06b307d657872b8fb14188e Mon Sep 17 00:00:00 2001 From: Bulat Bochkariov Date: Mon, 23 Sep 2019 22:02:27 -0700 Subject: [PATCH 168/388] Fix a typo --- docs/index.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.txt b/docs/index.txt index 4e8bc17d..27008a0d 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -141,7 +141,7 @@ Backporting process git clone https://github.com/python/cpython.git git clone https://github.com/testing-cabal/mock.git - Make sure they both on master and up to date! + Make sure they are both on master and up to date! 2. Create a branch in your ``mock`` clone and switch to it. From d6b42149bb87cf38729eef8a100c473f602ef7fa Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 16 Oct 2019 08:22:07 +0100 Subject: [PATCH 169/388] this started failing when pypy 7.2 was released --- mock/tests/testmock.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 5f6045af..15bac2ec 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -826,6 +826,7 @@ 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) From 064c55c5b3026bcd7e29b1cf942912654777d6c4 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 16 Oct 2019 08:22:18 +0100 Subject: [PATCH 170/388] link to the pypy issue --- mock/tests/testhelpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index d56a47f0..a5654ada 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -974,7 +974,7 @@ def check_data_descriptor(mock_attr): @unittest.skipIf('PyPy' in sys.version and sys.version_info > (3, 0), - "See https://github.com/testing-cabal/mock/issues/452") + "https://bitbucket.org/pypy/pypy/issues/3010") def test_autospec_on_bound_builtin_function(self): meth = six.create_bound_method(time.ctime, time.time()) self.assertIsInstance(meth(), str) From 57228528e69372cf622fc8a84f15302467d1a0bb Mon Sep 17 00:00:00 2001 From: Drew H Date: Thu, 7 Nov 2019 14:50:40 -0800 Subject: [PATCH 171/388] Remove redundant license option and fix typo --- setup.cfg | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 7283b793..42ba277c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,8 +5,7 @@ home-page = http://mock.readthedocs.org/en/latest/ description-file = README.rst author = Testing Cabal author-email = testing-in-python@lists.idyll.org -license = OSI Approved :: BSD License -classifier = +classifiers = Development Status :: 5 - Production/Stable Environment :: Console Intended Audience :: Developers From d1118fbc2a6044121625a9bc4a9a46a308b08f01 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 13 Jan 2020 08:00:48 +0000 Subject: [PATCH 172/388] 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 173/388] 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 174/388] 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 175/388] 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 176/388] 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 177/388] 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 178/388] 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 179/388] 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 180/388] 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 181/388] 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 182/388] 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 183/388] 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 184/388] 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 185/388] 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 186/388] 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 187/388] 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 188/388] 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 189/388] 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 190/388] 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 191/388] 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 192/388] 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 193/388] 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 194/388] 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 195/388] 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 196/388] 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 197/388] 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 198/388] 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 199/388] 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 200/388] 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 201/388] 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 202/388] 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 203/388] 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 204/388] 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 205/388] 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 206/388] 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 207/388] 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 208/388] 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 209/388] 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 210/388] 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 211/388] 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 212/388] 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 213/388] 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 214/388] 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 215/388] 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 216/388] 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 217/388] 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 218/388] 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 219/388] 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 220/388] 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 221/388] 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 222/388] 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 223/388] 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 224/388] 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 225/388] 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 226/388] 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 227/388] 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 228/388] 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 229/388] 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 230/388] 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 231/388] 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 232/388] 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 233/388] 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 234/388] 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 235/388] 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 236/388] 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 237/388] 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 238/388] 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 239/388] 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 240/388] 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 241/388] 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. From 35c733d6128877141e06a28ed670535f67580455 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 29 Jan 2020 19:53:11 +0000 Subject: [PATCH 242/388] fixup: package checks --- .circleci/config.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d3c62843..395c8faa 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -58,12 +58,6 @@ common: &common branches: only: master - - check-package: - name: check-package-python27 - image: circleci/python:3.7 - requires: - - package - - check-package: name: check-package-python37 image: circleci/python:3.7 @@ -78,7 +72,7 @@ common: &common - check-package: name: check-package-pypy36 - image: pypy:2.7 + image: pypy:3.6 python: pypy requires: - package From 4bc7455e26f2cef983d878a7a46ce961d961888e Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 29 Jan 2020 19:56:18 +0000 Subject: [PATCH 243/388] paranoid packaging check for 3.6 --- .circleci/config.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 395c8faa..1e51e9f8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -59,7 +59,13 @@ common: &common only: master - check-package: - name: check-package-python37 + name: check-package-python36 + image: circleci/python:3.6 + requires: + - package + + - check-package: + name: check-package-python36 image: circleci/python:3.7 requires: - package @@ -81,6 +87,7 @@ common: &common name: release config: .carthorse.yml requires: + - check-package-python36 - check-package-python37 - check-package-python38 - check-package-pypy36 From 7a4a7d20bbdd0da2ffa945e91969afd639da67e6 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 29 Jan 2020 20:48:51 +0000 Subject: [PATCH 244/388] another packaging test bugfix --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1e51e9f8..c470185e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -79,7 +79,6 @@ common: &common - check-package: name: check-package-pypy36 image: pypy:3.6 - python: pypy requires: - package From 8fb893c3485eee683348e113f90334b6341c99a3 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 29 Jan 2020 20:50:11 +0000 Subject: [PATCH 245/388] *sigh* --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c470185e..ca016234 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -65,7 +65,7 @@ common: &common - package - check-package: - name: check-package-python36 + name: check-package-python37 image: circleci/python:3.7 requires: - package From 87ca4065d461e46430070a0f1680e45c9c80a1f0 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 29 Jan 2020 21:14:42 +0000 Subject: [PATCH 246/388] this skip is no longer needed --- mock/tests/testhelpers.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index eea9fe2b..2a28796f 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -502,8 +502,6 @@ 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): From 9e5e0380626fd3c540aa4799df0e794cf24d16aa Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 29 Jan 2020 21:14:49 +0000 Subject: [PATCH 247/388] *sigh* --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index ca016234..9855ef71 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -79,6 +79,7 @@ common: &common - check-package: name: check-package-pypy36 image: pypy:3.6 + python: pypy3 requires: - package From ea9f71536da0ce3bd31e31b5f428f3495c6ab0dc Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 5 Feb 2020 08:13:41 +0000 Subject: [PATCH 248/388] Prepare for 4.0.0 release --- CHANGELOG.rst | 5 +++++ mock/__init__.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7f14e60a..d3efd61f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,8 @@ +4.0.0 +----- + +- No Changes from 4.0.0b1. + 4.0.0b1 ------- diff --git a/mock/__init__.py b/mock/__init__.py index cdf5a163..3624b223 100644 --- a/mock/__init__.py +++ b/mock/__init__.py @@ -7,7 +7,7 @@ import mock.mock as _mock from mock.mock import * -__version__ = '4.0.0b1' +__version__ = '4.0.0' version_info = tuple(int(p) for p in re.match(r'(\d+).(\d+).(\d+)', __version__).groups()) From bab8ceab6219866b1589bec37e74c7a6dd85bc80 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 6 Feb 2020 15:42:01 +0000 Subject: [PATCH 249/388] Remove universal wheel tag and prepare for a 4.0.1 release --- CHANGELOG.rst | 5 +++++ mock/__init__.py | 2 +- setup.cfg | 3 --- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d3efd61f..adf3e289 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,8 @@ +4.0.1 +----- + +- Remove the universal marker from the wheel. + 4.0.0 ----- diff --git a/mock/__init__.py b/mock/__init__.py index 3624b223..1b404679 100644 --- a/mock/__init__.py +++ b/mock/__init__.py @@ -7,7 +7,7 @@ import mock.mock as _mock from mock.mock import * -__version__ = '4.0.0' +__version__ = '4.0.1' version_info = tuple(int(p) for p in re.match(r'(\d+).(\d+).(\d+)', __version__).groups()) diff --git a/setup.cfg b/setup.cfg index d70c78e8..e25e6cc4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -38,9 +38,6 @@ build = wheel blurb -[bdist_wheel] -universal = 1 - [tool:pytest] python_files=test*.py filterwarnings = From 742b7f025cfe641fcbb8ef6b1043d00ccfdcf840 Mon Sep 17 00:00:00 2001 From: Karthikeyan Singaravelan Date: Wed, 11 Mar 2020 20:36:12 +0530 Subject: [PATCH 250/388] bpo-39915: Ensure await_args_list is updated according to the order in which coroutines were awaited (GH-18924) Create call objects with awaited arguments instead of using call_args which has only last call value. Backports: e553f204bf0e39b1d701a364bc71b286acb9433f Signed-off-by: Chris Withers --- NEWS.d/2020-03-10-19-38-47.bpo-39915.CjPeiY.rst | 4 ++++ mock/mock.py | 2 +- mock/tests/testasync.py | 11 +++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 NEWS.d/2020-03-10-19-38-47.bpo-39915.CjPeiY.rst diff --git a/NEWS.d/2020-03-10-19-38-47.bpo-39915.CjPeiY.rst b/NEWS.d/2020-03-10-19-38-47.bpo-39915.CjPeiY.rst new file mode 100644 index 00000000..2c369474 --- /dev/null +++ b/NEWS.d/2020-03-10-19-38-47.bpo-39915.CjPeiY.rst @@ -0,0 +1,4 @@ +Ensure :attr:`unittest.mock.AsyncMock.await_args_list` has call objects in +the order of awaited arguments instead of using +:attr:`unittest.mock.Mock.call_args` which has the last value of the call. +Patch by Karthikeyan Singaravelan. diff --git a/mock/mock.py b/mock/mock.py index e91920dd..47666723 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2186,7 +2186,7 @@ async def _execute_mock_call(_mock_self, *args, **kwargs): # This is nearly just like super(), except for special handling # of coroutines - _call = self.call_args + _call = _Call((args, kwargs), two=True) self.await_count += 1 self.await_args = _call self.await_args_list.append(_call) diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index 8afa49c1..9fd2b656 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -513,6 +513,17 @@ def inner(): mock.assert_awaited() self.assertTrue(ran) + async def test_await_args_list_order(self): + async_mock = AsyncMock() + mock2 = async_mock(2) + mock1 = async_mock(1) + await mock1 + await mock2 + async_mock.assert_has_awaits([call(1), call(2)]) + self.assertEqual(async_mock.await_args_list, [call(1), call(2)]) + self.assertEqual(async_mock.call_args_list, [call(2), call(1)]) + + class AsyncMagicMethods(unittest.TestCase): def test_async_magic_methods_return_async_mocks(self): m_mock = MagicMock() From d06f48d677ffd387fab97a628d9d96613f6af065 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 11 Mar 2020 17:04:44 +0000 Subject: [PATCH 251/388] latest sync point --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index 823b97bd..c2edac8e 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -db5e86adbce12350c26e7ffc2c6673369971a2dc +e553f204bf0e39b1d701a364bc71b286acb9433f From 87546bf7b90531e70419e6343c0938dcab3fb36b Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 11 Mar 2020 17:08:10 +0000 Subject: [PATCH 252/388] Preparing for 4.0.2 release. --- CHANGELOG.rst | 8 ++++++++ NEWS.d/2020-03-10-19-38-47.bpo-39915.CjPeiY.rst | 4 ---- mock/__init__.py | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) delete mode 100644 NEWS.d/2020-03-10-19-38-47.bpo-39915.CjPeiY.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index adf3e289..7439fe58 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,11 @@ +4.0.2 +----- + +- Issue #39915: Ensure :attr:`unittest.mock.AsyncMock.await_args_list` has + call objects in the order of awaited arguments instead of using + :attr:`unittest.mock.Mock.call_args` which has the last value of the call. + Patch by Karthikeyan Singaravelan. + 4.0.1 ----- diff --git a/NEWS.d/2020-03-10-19-38-47.bpo-39915.CjPeiY.rst b/NEWS.d/2020-03-10-19-38-47.bpo-39915.CjPeiY.rst deleted file mode 100644 index 2c369474..00000000 --- a/NEWS.d/2020-03-10-19-38-47.bpo-39915.CjPeiY.rst +++ /dev/null @@ -1,4 +0,0 @@ -Ensure :attr:`unittest.mock.AsyncMock.await_args_list` has call objects in -the order of awaited arguments instead of using -:attr:`unittest.mock.Mock.call_args` which has the last value of the call. -Patch by Karthikeyan Singaravelan. diff --git a/mock/__init__.py b/mock/__init__.py index 1b404679..180dee55 100644 --- a/mock/__init__.py +++ b/mock/__init__.py @@ -7,7 +7,7 @@ import mock.mock as _mock from mock.mock import * -__version__ = '4.0.1' +__version__ = '4.0.2' version_info = tuple(int(p) for p in re.match(r'(\d+).(\d+).(\d+)', __version__).groups()) From b5ce0a5c4d372b77deff46fec8edf974d7d1f875 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 11 Mar 2020 17:10:35 +0000 Subject: [PATCH 253/388] Add intersphinx. --- docs/conf.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 0197463d..e978b466 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -26,7 +26,13 @@ # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.doctest'] +extensions = [ + 'sphinx.ext.intersphinx', + ] + +intersphinx_mapping = { + 'python': ('http://docs.python.org/dev', None), +} doctest_global_setup = """ import os From 27d6349c033d188abfc2712ded6782d11f7d6104 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sat, 14 Mar 2020 09:46:22 +0200 Subject: [PATCH 254/388] https://github.com/pytest-dev/pytest/issues/6924 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index e25e6cc4..06918091 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,7 +31,7 @@ packages = mock docs = sphinx test = - pytest + pytest<5.4 pytest-cov build = twine From a7002f30f9098b8767cc42247e1aefa19b9f35d7 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 3 Jun 2020 11:34:25 +0100 Subject: [PATCH 255/388] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..bc950b71 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,30 @@ +--- +name: Bug report +about: Only report bugs here that are specific to this backport. +title: '' +labels: '' +assignees: '' + +--- + +This package is a rolling backport of [`unittest.mock`](https://github.com/python/cpython/blob/master/Lib/unittest/mock.py). +As such, any problems you encounter most likely need to be fixed upstream. + +Before submitting an issue here, please try and reproduce the problem on the latest release of Python 3, including alphas, and replace any import from `mock` with ones from `unittest.mock`. + +If the issue still occurs, then please report upstream through https://bugs.python.org/ as it will need to be fixed there so that it can be backported here and released to you. + +If the issue does not occur upstream, please file an issue using the template below as it may be an issue specific to the backport: + +**What versions are you using?** + - Python: [e.g. 3.7.1] + - Mock: [e.g. 4.0.2] + - Operating System: [e.g.Linux, macOS, Windows] + +**What happened?** + + + +**What were you hoping to happen instead?** From 902eea1b1d5c8652d9ac34dcce7a2cc08f3be76d Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 11 Apr 2020 10:59:24 +0300 Subject: [PATCH 256/388] bpo-40126: Fix reverting multiple patches in unittest.mock. (GH-19351) Patcher's __exit__() is now never called if its __enter__() is failed. Returning true from __exit__() silences now the exception. Backports: 4b222c9491d1700e9bdd98e6889b8d0ea1c7321e Signed-off-by: Chris Withers --- .../2020-04-04-00-47-40.bpo-40126.Y-bTNP.rst | 3 + mock/mock.py | 74 +++++++------------ mock/tests/testpatch.py | 2 +- 3 files changed, 30 insertions(+), 49 deletions(-) create mode 100644 NEWS.d/2020-04-04-00-47-40.bpo-40126.Y-bTNP.rst diff --git a/NEWS.d/2020-04-04-00-47-40.bpo-40126.Y-bTNP.rst b/NEWS.d/2020-04-04-00-47-40.bpo-40126.Y-bTNP.rst new file mode 100644 index 00000000..8f725cfb --- /dev/null +++ b/NEWS.d/2020-04-04-00-47-40.bpo-40126.Y-bTNP.rst @@ -0,0 +1,3 @@ +Fixed reverting multiple patches in unittest.mock. Patcher's ``__exit__()`` +is now never called if its ``__enter__()`` is failed. Returning true from +``__exit__()`` silences now the exception. diff --git a/mock/mock.py b/mock/mock.py index 47666723..523d61ef 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1250,11 +1250,6 @@ def _importer(target): return thing -def _is_started(patcher): - # XXXX horrible - return hasattr(patcher, 'is_local') - - class _patch(object): attribute_name = None @@ -1325,14 +1320,9 @@ def decorate_class(self, klass): @contextlib.contextmanager def decoration_helper(self, patched, args, keywargs): extra_args = [] - entered_patchers = [] - patching = None - - exc_info = tuple() - try: + with contextlib.ExitStack() as exit_stack: for patching in patched.patchings: - arg = patching.__enter__() - entered_patchers.append(patching) + arg = exit_stack.enter_context(patching) if patching.attribute_name is not None: keywargs.update(arg) elif patching.new is DEFAULT: @@ -1340,19 +1330,6 @@ def decoration_helper(self, patched, args, keywargs): 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): @@ -1529,25 +1506,26 @@ def __enter__(self): self.temp_original = original self.is_local = local - setattr(self.target, self.attribute, new_attr) - if self.attribute_name is not None: - extra_args = {} - if self.new is DEFAULT: - extra_args[self.attribute_name] = new - for patching in self.additional_patchers: - arg = patching.__enter__() - if patching.new is DEFAULT: - extra_args.update(arg) - return extra_args - - return new - + self._exit_stack = contextlib.ExitStack() + try: + setattr(self.target, self.attribute, new_attr) + if self.attribute_name is not None: + extra_args = {} + if self.new is DEFAULT: + extra_args[self.attribute_name] = new + for patching in self.additional_patchers: + arg = self._exit_stack.enter_context(patching) + if patching.new is DEFAULT: + extra_args.update(arg) + return extra_args + + return new + except: + if not self.__exit__(*sys.exc_info()): + raise def __exit__(self, *exc_info): """Undo the patch.""" - if not _is_started(self): - return - if self.is_local and self.temp_original is not DEFAULT: setattr(self.target, self.attribute, self.temp_original) else: @@ -1562,9 +1540,9 @@ def __exit__(self, *exc_info): del self.temp_original del self.is_local del self.target - for patcher in reversed(self.additional_patchers): - if _is_started(patcher): - patcher.__exit__(*exc_info) + exit_stack = self._exit_stack + del self._exit_stack + return exit_stack.__exit__(*exc_info) def start(self): @@ -1580,9 +1558,9 @@ def stop(self): self._active_patches.remove(self) except ValueError: # If the patch hasn't been started this will fail - pass + return None - return self.__exit__() + return self.__exit__(None, None, None) @@ -1882,9 +1860,9 @@ def stop(self): _patch._active_patches.remove(self) except ValueError: # If the patch hasn't been started this will fail - pass + return None - return self.__exit__() + return self.__exit__(None, None, None) def _clear_dict(in_dict): diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index fbf4a537..070d7e81 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -774,7 +774,7 @@ 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.assertFalse(patcher.stop()) self.assertEqual(d, original) From c627515e5b6405b4c712f997b2f1e80c2778d79d Mon Sep 17 00:00:00 2001 From: Karthikeyan Singaravelan Date: Wed, 29 Apr 2020 00:52:31 +0530 Subject: [PATCH 257/388] bpo-39966: Revert "bpo-25597: Ensure wraps' return value is used for magic methods in MagicMock" (GH-19734) * Revert "bpo-25597: Ensure wraps' return value is used for magic methods in MagicMock (#16029)" This reverts commit 72b1004657e60c900e4cd031b2635b587f4b280e. Backports: 521c8d6806adf0305c158d280ec00cca48e8ab22 Signed-off-by: Chris Withers --- NEWS.d/2020-04-27-14-48-43.bpo-39966.N5yXUe.rst | 2 ++ mock/mock.py | 6 ------ mock/tests/testmock.py | 16 ++++++++++------ 3 files changed, 12 insertions(+), 12 deletions(-) create mode 100644 NEWS.d/2020-04-27-14-48-43.bpo-39966.N5yXUe.rst diff --git a/NEWS.d/2020-04-27-14-48-43.bpo-39966.N5yXUe.rst b/NEWS.d/2020-04-27-14-48-43.bpo-39966.N5yXUe.rst new file mode 100644 index 00000000..614b4520 --- /dev/null +++ b/NEWS.d/2020-04-27-14-48-43.bpo-39966.N5yXUe.rst @@ -0,0 +1,2 @@ +Revert bpo-25597. :class:`unittest.mock.MagicMock` with wraps' set uses +default return values for magic methods. diff --git a/mock/mock.py b/mock/mock.py index 523d61ef..c5fe53fb 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2025,12 +2025,6 @@ 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 8bb87594..bdc24e3b 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -716,12 +716,16 @@ def method(self): pass def test_magic_method_wraps_dict(self): + # bpo-25597: MagicMock with wrap doesn't call wrapped object's + # method for magic methods with default values. 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) + # Accessing key gives a MagicMock + self.assertIsInstance(wrapped_dict['foo'], MagicMock) + # __contains__ method has a default value of False + self.assertFalse('foo' in wrapped_dict) # return_value is non-sentinel and takes precedence over wrapped value. wrapped_dict.get.return_value = 'return_value' @@ -732,14 +736,13 @@ def test_magic_method_wraps_dict(self): self.assertEqual(wrapped_dict.get('foo'), 'bar') self.assertEqual(wrapped_dict.get('baz'), None) - with self.assertRaises(KeyError): - wrapped_dict['baz'] + self.assertIsInstance(wrapped_dict['baz'], MagicMock) 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) + self.assertIsInstance(wrapped_dict['baz'], MagicMock) + self.assertFalse('bar' in wrapped_dict) del data['baz'] self.assertEqual(wrapped_dict.get('baz'), None) @@ -759,6 +762,7 @@ def __custom_method__(self): klass = MagicMock(wraps=Foo) obj = klass() self.assertEqual(obj.__getitem__(2), 2) + self.assertEqual(obj[2], 2) self.assertEqual(obj.__custom_method__(), "foo") From e5d7551a3f1247f7020fd2d78893893aea3aaac1 Mon Sep 17 00:00:00 2001 From: vabr-g Date: Thu, 5 Nov 2020 18:04:38 +0100 Subject: [PATCH 258/388] bpo-41877 Check for asert, aseert, assrt in mocks (GH-23165) Currently, a Mock object which is not unsafe will raise an AttributeError if an attribute with the prefix assert or assret is accessed on it. This protects against misspellings of real assert method calls, which lead to tests passing silently even if the tested code does not satisfy the intended assertion. Recently a check was done in a large code base (Google) and three more frequent ways of misspelling assert were found causing harm: asert, aseert, assrt. These are now added to the existing check. Backports: 4662fa9bfe4a849fe87bfb321d8ef0956c89a772 Signed-off-by: Chris Withers --- NEWS.d/2020-11-05-16-00-03.bpo-41877.FHbngM.rst | 2 ++ mock/mock.py | 4 ++-- mock/tests/testmock.py | 11 ++++++++++- 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 NEWS.d/2020-11-05-16-00-03.bpo-41877.FHbngM.rst diff --git a/NEWS.d/2020-11-05-16-00-03.bpo-41877.FHbngM.rst b/NEWS.d/2020-11-05-16-00-03.bpo-41877.FHbngM.rst new file mode 100644 index 00000000..033bea8f --- /dev/null +++ b/NEWS.d/2020-11-05-16-00-03.bpo-41877.FHbngM.rst @@ -0,0 +1,2 @@ +Mock objects which are not unsafe will now raise an AttributeError if an attribute with the prefix asert, aseert, +or assrt is accessed, in addition to this already happening for the prefixes assert or assret. \ No newline at end of file diff --git a/mock/mock.py b/mock/mock.py index c5fe53fb..e4c20f93 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -633,9 +633,9 @@ def __getattr__(self, name): elif _is_magic(name): raise AttributeError(name) if not self._mock_unsafe: - if name.startswith(('assert', 'assret')): + if name.startswith(('assert', 'assret', 'asert', 'aseert', 'assrt')): raise AttributeError("Attributes cannot start with 'assert' " - "or 'assret'") + "or its misspellings") result = self._mock_children.get(name) if result is _deleted: diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index bdc24e3b..2293cfaa 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -1600,14 +1600,23 @@ def static_method(): pass #Issue21238 def test_mock_unsafe(self): m = Mock() - msg = "Attributes cannot start with 'assert' or 'assret'" + msg = "Attributes cannot start with 'assert' or its misspellings" with self.assertRaisesRegex(AttributeError, msg): m.assert_foo_call() with self.assertRaisesRegex(AttributeError, msg): m.assret_foo_call() + with self.assertRaisesRegex(AttributeError, msg): + m.asert_foo_call() + with self.assertRaisesRegex(AttributeError, msg): + m.aseert_foo_call() + with self.assertRaisesRegex(AttributeError, msg): + m.assrt_foo_call() m = Mock(unsafe=True) m.assert_foo_call() m.assret_foo_call() + m.asert_foo_call() + m.aseert_foo_call() + m.assrt_foo_call() #Issue21262 def test_assert_not_called(self): From f7e3ea85b4f99604e8e05f4659825f69f3f29bc6 Mon Sep 17 00:00:00 2001 From: idanw206 <31290383+idanw206@users.noreply.github.com> Date: Sun, 6 Dec 2020 11:59:36 +0200 Subject: [PATCH 259/388] bpo-42532: Check if NonCallableMock's spec_arg is not None instead of call its __bool__ function (GH23613) Check if NonCallableMock's spec_arg is not None instead of call its __bool__ function Backports: c598a04dd29b89ad072245ddaf738badcfb41ac7 Signed-off-by: Chris Withers --- NEWS.d/2020-12-02-07-37-59.bpo-42532.ObNep_.rst | 1 + mock/mock.py | 2 +- mock/tests/testmock.py | 10 ++++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 NEWS.d/2020-12-02-07-37-59.bpo-42532.ObNep_.rst diff --git a/NEWS.d/2020-12-02-07-37-59.bpo-42532.ObNep_.rst b/NEWS.d/2020-12-02-07-37-59.bpo-42532.ObNep_.rst new file mode 100644 index 00000000..7465cb8e --- /dev/null +++ b/NEWS.d/2020-12-02-07-37-59.bpo-42532.ObNep_.rst @@ -0,0 +1 @@ +Remove unexpected call of ``__bool__`` when passing a ``spec_arg`` argument to a Mock. diff --git a/mock/mock.py b/mock/mock.py index e4c20f93..6ba80d7c 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -408,7 +408,7 @@ def __new__(cls, *args, **kw): # Check if spec is an async object or function 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): + if spec_arg is not None and _is_async_obj(spec_arg): bases = (AsyncMockMixin, cls) new = type(cls.__name__, bases, {'__doc__': cls.__doc__}) instance = _safe_super(NonCallableMock, cls).__new__(new) diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 2293cfaa..5702b6da 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -2167,6 +2167,16 @@ def trace(frame, event, arg): # pragma: no cover obj = mock(spec=Something) self.assertIsInstance(obj, Something) + def test_bool_not_called_when_passing_spec_arg(self): + class Something: + def __init__(self): + self.obj_with_bool_func = unittest.mock.MagicMock() + + obj = Something() + with unittest.mock.patch.object(obj, 'obj_with_bool_func', autospec=True): pass + + self.assertEqual(obj.obj_with_bool_func.__bool__.call_count, 0) + if __name__ == '__main__': unittest.main() From 841c3313956c23b8c4ebeb367fb7ca3558e8991e Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 10 Dec 2020 07:17:00 +0000 Subject: [PATCH 260/388] latest sync point --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index c2edac8e..95f4a893 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -e553f204bf0e39b1d701a364bc71b286acb9433f +c598a04dd29b89ad072245ddaf738badcfb41ac7 From 93cf533a95abc697ad36c8d1ddef82058b884425 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 10 Dec 2020 07:29:09 +0000 Subject: [PATCH 261/388] Preparing for 4.0.3 release. --- CHANGELOG.rst | 18 ++++++++++++++++++ .../2020-04-04-00-47-40.bpo-40126.Y-bTNP.rst | 3 --- .../2020-04-27-14-48-43.bpo-39966.N5yXUe.rst | 2 -- .../2020-11-05-16-00-03.bpo-41877.FHbngM.rst | 2 -- .../2020-12-02-07-37-59.bpo-42532.ObNep_.rst | 1 - mock/__init__.py | 2 +- 6 files changed, 19 insertions(+), 9 deletions(-) delete mode 100644 NEWS.d/2020-04-04-00-47-40.bpo-40126.Y-bTNP.rst delete mode 100644 NEWS.d/2020-04-27-14-48-43.bpo-39966.N5yXUe.rst delete mode 100644 NEWS.d/2020-11-05-16-00-03.bpo-41877.FHbngM.rst delete mode 100644 NEWS.d/2020-12-02-07-37-59.bpo-42532.ObNep_.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7439fe58..f69bbefe 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,21 @@ +4.0.3 +----- + +- Issue #42532: Remove unexpected call of ``__bool__`` when passing a + ``spec_arg`` argument to a Mock. + +- Issue #39966: Revert bpo-25597. :class:`unittest.mock.MagicMock` with + wraps' set uses default return values for magic methods. + +- Issue #41877: Mock objects which are not unsafe will now raise an + AttributeError if an attribute with the prefix asert, aseert, or assrt is + accessed, in addition to this already happening for the prefixes assert or + assret. + +- Issue #40126: Fixed reverting multiple patches in unittest.mock. Patcher's + ``__exit__()`` is now never called if its ``__enter__()`` is failed. + Returning true from ``__exit__()`` silences now the exception. + 4.0.2 ----- diff --git a/NEWS.d/2020-04-04-00-47-40.bpo-40126.Y-bTNP.rst b/NEWS.d/2020-04-04-00-47-40.bpo-40126.Y-bTNP.rst deleted file mode 100644 index 8f725cfb..00000000 --- a/NEWS.d/2020-04-04-00-47-40.bpo-40126.Y-bTNP.rst +++ /dev/null @@ -1,3 +0,0 @@ -Fixed reverting multiple patches in unittest.mock. Patcher's ``__exit__()`` -is now never called if its ``__enter__()`` is failed. Returning true from -``__exit__()`` silences now the exception. diff --git a/NEWS.d/2020-04-27-14-48-43.bpo-39966.N5yXUe.rst b/NEWS.d/2020-04-27-14-48-43.bpo-39966.N5yXUe.rst deleted file mode 100644 index 614b4520..00000000 --- a/NEWS.d/2020-04-27-14-48-43.bpo-39966.N5yXUe.rst +++ /dev/null @@ -1,2 +0,0 @@ -Revert bpo-25597. :class:`unittest.mock.MagicMock` with wraps' set uses -default return values for magic methods. diff --git a/NEWS.d/2020-11-05-16-00-03.bpo-41877.FHbngM.rst b/NEWS.d/2020-11-05-16-00-03.bpo-41877.FHbngM.rst deleted file mode 100644 index 033bea8f..00000000 --- a/NEWS.d/2020-11-05-16-00-03.bpo-41877.FHbngM.rst +++ /dev/null @@ -1,2 +0,0 @@ -Mock objects which are not unsafe will now raise an AttributeError if an attribute with the prefix asert, aseert, -or assrt is accessed, in addition to this already happening for the prefixes assert or assret. \ No newline at end of file diff --git a/NEWS.d/2020-12-02-07-37-59.bpo-42532.ObNep_.rst b/NEWS.d/2020-12-02-07-37-59.bpo-42532.ObNep_.rst deleted file mode 100644 index 7465cb8e..00000000 --- a/NEWS.d/2020-12-02-07-37-59.bpo-42532.ObNep_.rst +++ /dev/null @@ -1 +0,0 @@ -Remove unexpected call of ``__bool__`` when passing a ``spec_arg`` argument to a Mock. diff --git a/mock/__init__.py b/mock/__init__.py index 180dee55..dbe8031b 100644 --- a/mock/__init__.py +++ b/mock/__init__.py @@ -7,7 +7,7 @@ import mock.mock as _mock from mock.mock import * -__version__ = '4.0.2' +__version__ = '4.0.3' version_info = tuple(int(p) for p in re.match(r'(\d+).(\d+).(\d+)', __version__).groups()) From c3153c8f572b689b951482f599fa5faded012dc7 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 10 Dec 2020 08:34:21 +0000 Subject: [PATCH 262/388] switch to token auth --- .carthorse.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.carthorse.yml b/.carthorse.yml index d8f3a9a0..7b6ca85b 100644 --- a/.carthorse.yml +++ b/.carthorse.yml @@ -5,5 +5,5 @@ carthorse: - version-not-tagged actions: - run: "sudo pip install -e .[build]" - - run: "twine upload -u carthorse-mock -p $PYPI_PASS dist/*" + - run: "twine upload -u __token__ -p $PYPI_TOKEN dist/*" - create-tag From 304e8e911c9943729763c4d04bbbb1ea0cad45a0 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sat, 10 Apr 2021 20:06:10 +0100 Subject: [PATCH 263/388] move to matrix and add 3.9 --- .circleci/config.yml | 59 ++++++++++++++------------------------------ 1 file changed, 19 insertions(+), 40 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9855ef71..4564ec22 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -24,26 +24,21 @@ jobs: common: &common jobs: + - python/pip-run-tests: - name: python36 - image: circleci/python:3.6 - - python/pip-run-tests: - name: python37 - image: circleci/python:3.7 - - python/pip-run-tests: - name: python38 - image: circleci/python:3.8 - - python/pip-run-tests: - name: pypy36 - image: pypy:3.6 + matrix: + parameters: + image: + - circleci/python:3.6 + - circleci/python:3.7 + - circleci/python:3.8 + - circleci/python:3.9 + - pypy:3.6 - python/coverage: name: coverage requires: - - python36 - - python37 - - python38 - - pypy36 + - python/pip-run-tests - python/pip-docs: name: docs @@ -59,27 +54,14 @@ common: &common only: master - check-package: - name: check-package-python36 - image: circleci/python:3.6 - requires: - - package - - - check-package: - name: check-package-python37 - image: circleci/python:3.7 - requires: - - package - - - check-package: - name: check-package-python38 - image: circleci/python:3.8 - requires: - - package - - - check-package: - name: check-package-pypy36 - image: pypy:3.6 - python: pypy3 + matrix: + parameters: + image: + - circleci/python:3.6 + - circleci/python:3.7 + - circleci/python:3.8 + - circleci/python:3.9 + - pypy:3.6 requires: - package @@ -87,10 +69,7 @@ common: &common name: release config: .carthorse.yml requires: - - check-package-python36 - - check-package-python37 - - check-package-python38 - - check-package-pypy36 + - check-package workflows: push: From f41b906aba45d618b9f279f69b19d8a8a7b239e5 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 15 Apr 2021 08:23:18 +0100 Subject: [PATCH 264/388] remove unused tox.ini --- tox.ini | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 tox.ini diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 14eb4f43..00000000 --- a/tox.ini +++ /dev/null @@ -1,12 +0,0 @@ -[tox] -envlist = py36,py37,py38,docs - -[testenv] -commands = - {envbindir}/pytest {posargs} - -[testenv:docs] -deps = - sphinx -commands = - {envbindir}/python setup.py build_sphinx From f3e3d82aab0ede7e25273806dc0505574d85eae2 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 15 Apr 2021 08:27:25 +0100 Subject: [PATCH 265/388] fix tests that should test mock but were testing unittest.mock --- mock/tests/testmock.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 5702b6da..1a709092 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -13,6 +13,7 @@ create_autospec, mock ) from mock.mock import _Call, _CallList +import mock.mock as mock_module class Iter(object): @@ -47,7 +48,7 @@ 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 unittest.mock import *") + exec("from mock.mock import *") def test_constructor(self): @@ -2137,16 +2138,16 @@ def test_isinstance_under_settrace(self): # test_patch_dict_test_prefix and test_patch_test_prefix not restoring # causes the objects patched to go out of sync - old_patch = unittest.mock.patch + old_patch = mock_module.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(unittest.mock, 'patch', patch), + self.addCleanup(lambda patch: setattr(mock_module, 'patch', patch), old_patch) with patch.dict('sys.modules'): - del sys.modules['unittest.mock'] + del sys.modules['mock'] # This trace will stop coverage being measured ;-) def trace(frame, event, arg): # pragma: no cover @@ -2155,7 +2156,7 @@ def trace(frame, event, arg): # pragma: no cover self.addCleanup(sys.settrace, sys.gettrace()) sys.settrace(trace) - from unittest.mock import ( + from mock.mock import ( Mock, MagicMock, NonCallableMock, NonCallableMagicMock ) @@ -2170,10 +2171,10 @@ def trace(frame, event, arg): # pragma: no cover def test_bool_not_called_when_passing_spec_arg(self): class Something: def __init__(self): - self.obj_with_bool_func = unittest.mock.MagicMock() + self.obj_with_bool_func = mock_module.MagicMock() obj = Something() - with unittest.mock.patch.object(obj, 'obj_with_bool_func', autospec=True): pass + with mock_module.patch.object(obj, 'obj_with_bool_func', autospec=True): pass self.assertEqual(obj.obj_with_bool_func.__bool__.call_count, 0) From 7a9cba1d47a6ef32ddb5e2b744c21c12eec3a2ab Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Thu, 15 Apr 2021 08:45:18 +0100 Subject: [PATCH 266/388] Drop some axes off the package tests. Reduce CircleCI load, but also pypy has a different binary name, by the looks of it. --- .circleci/config.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4564ec22..76cc1589 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -58,10 +58,7 @@ common: &common parameters: image: - circleci/python:3.6 - - circleci/python:3.7 - - circleci/python:3.8 - circleci/python:3.9 - - pypy:3.6 requires: - package From c424e9e7916ca6e54732bda427be2f1eedc238e8 Mon Sep 17 00:00:00 2001 From: Andrii Oriekhov Date: Mon, 28 Feb 2022 18:53:02 +0200 Subject: [PATCH 267/388] add GitHub URL for PyPi --- setup.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.cfg b/setup.cfg index 06918091..2b0406f8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -20,6 +20,8 @@ classifiers = Topic :: Software Development :: Libraries Topic :: Software Development :: Libraries :: Python Modules Topic :: Software Development :: Testing +project_urls = + Source = https://github.com/testing-cabal/mock keyword = testing, test, mock, mocking, unittest, patching, stubs, fakes, doubles From b8ba8d78f54ae45438dd63c2e096e30138271f13 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 26 Dec 2022 20:20:56 +0000 Subject: [PATCH 268/388] Test 3.6 to 3.11 --- .circleci/config.yml | 18 ++++++++++-------- setup.cfg | 3 +++ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 76cc1589..0037c311 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,7 +1,7 @@ version: 2.1 orbs: - python: cjw296/python-ci@2 + python: cjw296/python-ci@3 jobs: check-package: @@ -29,11 +29,13 @@ common: &common matrix: parameters: image: - - circleci/python:3.6 - - circleci/python:3.7 - - circleci/python:3.8 - - circleci/python:3.9 - - pypy:3.6 + - cimg/python:3.6 + - cimg/python:3.7 + - cimg/python:3.8 + - cimg/python:3.9 + - cimg/python:3.10 + - cimg/python:3.11 + - pypy:3 - python/coverage: name: coverage @@ -57,8 +59,8 @@ common: &common matrix: parameters: image: - - circleci/python:3.6 - - circleci/python:3.9 + - cimg/python:3.6 + - cimg/python:3.11 requires: - package diff --git a/setup.cfg b/setup.cfg index 2b0406f8..9603beba 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,6 +15,9 @@ classifiers = Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: PyPy Topic :: Software Development :: Libraries From d6c4182a89768f71cc019f6ee3b77e09a4adfb24 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 26 Dec 2022 20:25:49 +0000 Subject: [PATCH 269/388] Sort out pytest versions. --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 9603beba..dfd0fa04 100644 --- a/setup.cfg +++ b/setup.cfg @@ -36,7 +36,7 @@ packages = mock docs = sphinx test = - pytest<5.4 + pytest pytest-cov build = twine From 8778b815bb97903caed5560ea0a384bdd57a466c Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 26 Dec 2022 20:33:41 +0000 Subject: [PATCH 270/388] Pin to older version of pypy that doesn't trigger bug. See https://foss.heptapod.net/pypy/pypy/-/issues/3436 --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0037c311..d55642ab 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -35,7 +35,7 @@ common: &common - cimg/python:3.9 - cimg/python:3.10 - cimg/python:3.11 - - pypy:3 + - pypy:3.7-7.3.2 # https://foss.heptapod.net/pypy/pypy/-/issues/3436 - python/coverage: name: coverage From a462852dfa0345c9a027386473ef87218f0233a7 Mon Sep 17 00:00:00 2001 From: vabr-g Date: Thu, 10 Dec 2020 19:35:28 +0100 Subject: [PATCH 271/388] bpo-41877: Improve docs for assert misspellings check in mock (GH-23729) This is a follow-up to https://github.com/python/cpython/commit/4662fa9bfe4a849fe87bfb321d8ef0956c89a772. That original commit expanded guards against misspelling assertions on mocks. This follow-up updates the documentation and improves the error message by pointing out the potential cause and solution. Automerge-Triggered-By: GH:gpshead Backports: 9fc571359af9320fddbe4aa2710a767f168c1707 Signed-off-by: Chris Withers --- NEWS.d/2020-12-10-09-24-44.bpo-41877.iJSCvM.rst | 1 + mock/mock.py | 5 +++-- mock/tests/testmock.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 NEWS.d/2020-12-10-09-24-44.bpo-41877.iJSCvM.rst diff --git a/NEWS.d/2020-12-10-09-24-44.bpo-41877.iJSCvM.rst b/NEWS.d/2020-12-10-09-24-44.bpo-41877.iJSCvM.rst new file mode 100644 index 00000000..df43cc5d --- /dev/null +++ b/NEWS.d/2020-12-10-09-24-44.bpo-41877.iJSCvM.rst @@ -0,0 +1 @@ +AttributeError for suspected misspellings of assertions on mocks are now pointing out that the cause are misspelled assertions and also what to do if the misspelling is actually an intended attribute name. The unittest.mock document is also updated to reflect the current set of recognised misspellings. \ No newline at end of file diff --git a/mock/mock.py b/mock/mock.py index 6ba80d7c..d8836471 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -634,8 +634,9 @@ def __getattr__(self, name): raise AttributeError(name) if not self._mock_unsafe: if name.startswith(('assert', 'assret', 'asert', 'aseert', 'assrt')): - raise AttributeError("Attributes cannot start with 'assert' " - "or its misspellings") + raise AttributeError( + f"{name} is not a valid assertion. Use a spec " + f"for the mock if {name} is meant to be an attribute.") result = self._mock_children.get(name) if result is _deleted: diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 1a709092..340c280f 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -1601,7 +1601,7 @@ def static_method(): pass #Issue21238 def test_mock_unsafe(self): m = Mock() - msg = "Attributes cannot start with 'assert' or its misspellings" + msg = "is not a valid assertion. Use a spec for the mock" with self.assertRaisesRegex(AttributeError, msg): m.assert_foo_call() with self.assertRaisesRegex(AttributeError, msg): From 9cf79b5c2a0be0955318e23f5adf9f24cdce1f75 Mon Sep 17 00:00:00 2001 From: vabr-g Date: Mon, 14 Dec 2020 19:30:09 +0100 Subject: [PATCH 272/388] bpo-41877: Check for misspelled speccing arguments (GH-23737) patch, patch.object and create_autospec silently ignore misspelled arguments such as autospect, auto_spec and set_spec. This can lead to tests failing to check what they are supposed to check. This change adds a check causing a RuntimeError if the above functions get any of the above misspellings as arguments. It also adds a new argument, "unsafe", which can be set to True to disable this check. Also add "!r" to format specifiers in added error messages. Backports: fdb9efce6ac211f973088eef508740c3fa2bd182 Signed-off-by: Chris Withers --- .../2020-12-10-19-49-52.bpo-41877.wiVlPc.rst | 1 + mock/mock.py | 39 +++++++++++--- mock/tests/testmock.py | 52 +++++++++++++++++++ 3 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 NEWS.d/2020-12-10-19-49-52.bpo-41877.wiVlPc.rst diff --git a/NEWS.d/2020-12-10-19-49-52.bpo-41877.wiVlPc.rst b/NEWS.d/2020-12-10-19-49-52.bpo-41877.wiVlPc.rst new file mode 100644 index 00000000..d42200ec --- /dev/null +++ b/NEWS.d/2020-12-10-19-49-52.bpo-41877.wiVlPc.rst @@ -0,0 +1 @@ +A check is added against misspellings of autospect, auto_spec and set_spec being passed as arguments to patch, patch.object and create_autospec. \ No newline at end of file diff --git a/mock/mock.py b/mock/mock.py index d8836471..519411f1 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -635,8 +635,8 @@ def __getattr__(self, name): if not self._mock_unsafe: if name.startswith(('assert', 'assret', 'asert', 'aseert', 'assrt')): raise AttributeError( - f"{name} is not a valid assertion. Use a spec " - f"for the mock if {name} is meant to be an attribute.") + f"{name!r} is not a valid assertion. Use a spec " + f"for the mock if {name!r} is meant to be an attribute.") result = self._mock_children.get(name) if result is _deleted: @@ -1251,6 +1251,17 @@ def _importer(target): return thing +# _check_spec_arg_typos takes kwargs from commands like patch and checks that +# they don't contain common misspellings of arguments related to autospeccing. +def _check_spec_arg_typos(kwargs_to_check): + typos = ("autospect", "auto_spec", "set_spec") + for typo in typos: + if typo in kwargs_to_check: + raise RuntimeError( + f"{typo!r} might be a typo; use unsafe=True if this is intended" + ) + + class _patch(object): attribute_name = None @@ -1258,7 +1269,7 @@ class _patch(object): def __init__( self, getter, attribute, new, spec, create, - spec_set, autospec, new_callable, kwargs + spec_set, autospec, new_callable, kwargs, *, unsafe=False ): if new_callable is not None: if new is not DEFAULT: @@ -1269,6 +1280,8 @@ def __init__( raise ValueError( "Cannot use 'autospec' and 'new_callable' together" ) + if not unsafe: + _check_spec_arg_typos(kwargs) self.getter = getter self.attribute = attribute @@ -1578,7 +1591,7 @@ def _get_target(target): def _patch_object( target, attribute, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, - new_callable=None, **kwargs + new_callable=None, *, unsafe=False, **kwargs ): """ patch the named member (`attribute`) on an object (`target`) with a mock @@ -1600,7 +1613,7 @@ def _patch_object( getter = lambda: target return _patch( getter, attribute, new, spec, create, - spec_set, autospec, new_callable, kwargs + spec_set, autospec, new_callable, kwargs, unsafe=unsafe ) @@ -1655,7 +1668,7 @@ def _patch_multiple(target, spec=None, create=False, spec_set=None, def patch( target, new=DEFAULT, spec=None, create=False, - spec_set=None, autospec=None, new_callable=None, **kwargs + spec_set=None, autospec=None, new_callable=None, *, unsafe=False, **kwargs ): """ `patch` acts as a function decorator, class decorator or a context @@ -1717,6 +1730,10 @@ def patch( use "as" then the patched object will be bound to the name after the "as"; very useful if `patch` is creating a mock object for you. + Patch will raise a `RuntimeError` if passed some common misspellings of + the arguments autospec and spec_set. Pass the argument `unsafe` with the + value True to disable that check. + `patch` takes arbitrary keyword arguments. These will be passed to `AsyncMock` if the patched object is asynchronous, to `MagicMock` otherwise or to `new_callable` if specified. @@ -1727,7 +1744,7 @@ def patch( getter, attribute = _get_target(target) return _patch( getter, attribute, new, spec, create, - spec_set, autospec, new_callable, kwargs + spec_set, autospec, new_callable, kwargs, unsafe=unsafe ) @@ -2590,7 +2607,7 @@ def call_list(self): def create_autospec(spec, spec_set=False, instance=False, _parent=None, - _name=None, **kwargs): + _name=None, *, unsafe=False, **kwargs): """Create a mock object using another object as a spec. Attributes on the mock will use the corresponding attribute on the `spec` object as their spec. @@ -2606,6 +2623,10 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, spec for an instance object by passing `instance=True`. The returned mock will only be callable if instances of the mock are callable. + `create_autospec` will raise a `RuntimeError` if passed some common + misspellings of the arguments autospec and spec_set. Pass the argument + `unsafe` with the value True to disable that check. + `create_autospec` also takes arbitrary keyword arguments that are passed to the constructor of the created mock.""" if _is_list(spec): @@ -2623,6 +2644,8 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, _kwargs = {} if _kwargs and instance: _kwargs['_spec_as_instance'] = True + if not unsafe: + _check_spec_arg_typos(kwargs) _kwargs.update(kwargs) diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 340c280f..0fd14d90 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -39,6 +39,12 @@ def cmeth(cls, a, b, c, d=None): pass def smeth(a, b, c, d=None): pass +class Typos(): + autospect = None + auto_spec = None + set_spec = None + + def something(a): pass @@ -2178,6 +2184,52 @@ def __init__(self): self.assertEqual(obj.obj_with_bool_func.__bool__.call_count, 0) + def test_misspelled_arguments(self): + class Foo(): + one = 'one' + # patch, patch.object and create_autospec need to check for misspelled + # arguments explicitly and throw a RuntimError if found. + with self.assertRaises(RuntimeError): + with patch(f'{__name__}.Something.meth', autospect=True): pass + with self.assertRaises(RuntimeError): + with patch.object(Foo, 'one', autospect=True): pass + with self.assertRaises(RuntimeError): + with patch(f'{__name__}.Something.meth', auto_spec=True): pass + with self.assertRaises(RuntimeError): + with patch.object(Foo, 'one', auto_spec=True): pass + with self.assertRaises(RuntimeError): + with patch(f'{__name__}.Something.meth', set_spec=True): pass + with self.assertRaises(RuntimeError): + with patch.object(Foo, 'one', set_spec=True): pass + with self.assertRaises(RuntimeError): + m = create_autospec(Foo, set_spec=True) + # patch.multiple, on the other hand, should flag misspelled arguments + # through an AttributeError, when trying to find the keys from kwargs + # as attributes on the target. + with self.assertRaises(AttributeError): + with patch.multiple( + f'{__name__}.Something', meth=DEFAULT, autospect=True): pass + with self.assertRaises(AttributeError): + with patch.multiple( + f'{__name__}.Something', meth=DEFAULT, auto_spec=True): pass + with self.assertRaises(AttributeError): + with patch.multiple( + f'{__name__}.Something', meth=DEFAULT, set_spec=True): pass + + with patch(f'{__name__}.Something.meth', unsafe=True, autospect=True): + pass + with patch.object(Foo, 'one', unsafe=True, autospect=True): pass + with patch(f'{__name__}.Something.meth', unsafe=True, auto_spec=True): + pass + with patch.object(Foo, 'one', unsafe=True, auto_spec=True): pass + with patch(f'{__name__}.Something.meth', unsafe=True, set_spec=True): + pass + with patch.object(Foo, 'one', unsafe=True, set_spec=True): pass + m = create_autospec(Foo, set_spec=True, unsafe=True) + with patch.multiple( + f'{__name__}.Typos', autospect=True, set_spec=True, auto_spec=True): + pass + if __name__ == '__main__': unittest.main() From c877db9ee1f09e0a744ec1e9ac9ad32162d7411b Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 27 Dec 2022 19:55:15 +0000 Subject: [PATCH 273/388] Add option to just list revs remaining to backport. --- backport.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/backport.py b/backport.py index b2ab5237..9bc10335 100644 --- a/backport.py +++ b/backport.py @@ -111,13 +111,20 @@ def main(): if args.skip_current: return skip_current(args.mock, args.skip_reason) + initial_cpython_rev = find_initial_cpython_rev() + + if args.list: + for rev in cpython_revs_affecting_mock(args.cpython, initial_cpython_rev): + print(git(f'show --name-only --oneline {rev}', args.cpython), end='') + has_been_backported(args.mock, rev) + print() + return + if repo_state_bad(args.mock): return cleanup_old_patches(args.mock) - initial_cpython_rev = find_initial_cpython_rev() - revs = cpython_revs_affecting_mock(args.cpython, initial_cpython_rev) for rev in revs: @@ -138,6 +145,7 @@ def parse_args(): parser = ArgumentParser() parser.add_argument('--cpython', default='../cpython') parser.add_argument('--mock', default=abspath(dirname(__file__))) + parser.add_argument('--list', action='store_true', help='list revs remaining to backport') parser.add_argument('--skip-current', action='store_true') parser.add_argument('--skip-reason', default='it has no changes needed here.') return parser.parse_args() From a5a9865335f4cb50ef46d9720bbba35ffeaffd17 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 27 Dec 2022 19:55:32 +0000 Subject: [PATCH 274/388] Look for test changes in new location as well as old location. --- backport.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backport.py b/backport.py index 9bc10335..ce3841fe 100644 --- a/backport.py +++ b/backport.py @@ -29,7 +29,10 @@ def find_initial_cpython_rev(): def cpython_revs_affecting_mock(cpython_repo, start): revs = git(f'log --no-merges --format=%H {start}.. ' - f'-- Lib/unittest/mock.py Lib/unittest/test/testmock/', + f'-- ' + f'Lib/unittest/mock.py ' + f'Lib/unittest/test/testmock/ ' + f'Lib/test/test_unittest/testmock/', repo=cpython_repo).split() revs.reverse() print(f'{len(revs)} patches that may need backporting') @@ -58,6 +61,7 @@ def munge(rev, patch): for pattern, sub in ( ('(a|b)/Lib/unittest/mock.py', r'\1/mock/mock.py'), (r'(a|b)/Lib/unittest/test/testmock/(\S+)', r'\1/mock/tests/\2'), + (r'(a|b)/Lib/test/test_unittest/testmock/(\S+)', r'\1/mock/tests/\2'), ('(a|b)/Misc/NEWS', r'\1/NEWS'), ('(a|b)/NEWS.d/next/[^/]+/(.+\.rst)', r'\1/NEWS.d/\2'), ): From 5e406f1e4ff4c43b4a8e4804fac0798ba99a2c54 Mon Sep 17 00:00:00 2001 From: Matthew Suozzo Date: Fri, 9 Apr 2021 23:45:50 -0400 Subject: [PATCH 275/388] bpo-43478: Restrict use of Mock objects as specs (GH-25326) * Restrict using Mock objects as specs as this is always a test bug where the resulting mock is misleadingly useless. * Skip a broken test that exposes a bug elsewhere in mock (noted in the original issue). Backports: dccdc500f9b5dab0a20407ae0178d393796a8828 Signed-off-by: Chris Withers --- .../2021-04-10-03-30-36.bpo-43478.iZcBTq.rst | 1 + lastsync.txt | 2 +- mock/mock.py | 43 +++++++++++++++++-- mock/tests/testasync.py | 4 +- mock/tests/testmock.py | 26 ++++++++++- 5 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 NEWS.d/2021-04-10-03-30-36.bpo-43478.iZcBTq.rst diff --git a/NEWS.d/2021-04-10-03-30-36.bpo-43478.iZcBTq.rst b/NEWS.d/2021-04-10-03-30-36.bpo-43478.iZcBTq.rst new file mode 100644 index 00000000..aaa1992f --- /dev/null +++ b/NEWS.d/2021-04-10-03-30-36.bpo-43478.iZcBTq.rst @@ -0,0 +1 @@ +Mocks can no longer be used as the specs for other Mocks. As a result, an already-mocked object cannot have an attribute mocked using `autospec=True` or be the subject of a `create_autospec(...)` call. This can uncover bugs in tests since these Mock-derived Mocks will always pass certain tests (e.g. isinstance) and builtin assert functions (e.g. assert_called_once_with) will unconditionally pass. \ No newline at end of file diff --git a/lastsync.txt b/lastsync.txt index 95f4a893..6392a86c 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -c598a04dd29b89ad072245ddaf738badcfb41ac7 +fdb9efce6ac211f973088eef508740c3fa2bd182 diff --git a/mock/mock.py b/mock/mock.py index 519411f1..17b133be 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -38,6 +38,11 @@ from mock import IS_PYPY from .backports import iscoroutinefunction + +class InvalidSpecError(Exception): + """Indicates that an invalid value was used as a mock spec.""" + + _builtins = {name for name in dir(builtins) if not name.startswith('_')} FILTER_DIR = True @@ -655,10 +660,17 @@ def __getattr__(self, name): self._mock_children[name] = result elif isinstance(result, _SpecState): - result = create_autospec( - result.spec, result.spec_set, result.instance, - result.parent, result.name - ) + try: + result = create_autospec( + result.spec, result.spec_set, result.instance, + result.parent, result.name + ) + except InvalidSpecError: + target_name = self.__dict__['_mock_name'] or self + raise InvalidSpecError( + f'Cannot autospec attr {name!r} from target ' + f'{target_name!r} as it has already been mocked out. ' + f'[target={self!r}, attr={result.spec!r}]') self._mock_children[name] = result return result @@ -1282,6 +1294,14 @@ def __init__( ) if not unsafe: _check_spec_arg_typos(kwargs) + if _is_instance_mock(spec): + raise InvalidSpecError( + f'Cannot spec attr {attribute!r} as the spec ' + f'has already been mocked out. [spec={spec!r}]') + if _is_instance_mock(spec_set): + raise InvalidSpecError( + f'Cannot spec attr {attribute!r} as the spec_set ' + f'target has already been mocked out. [spec_set={spec_set!r}]') self.getter = getter self.attribute = attribute @@ -1509,6 +1529,18 @@ def __enter__(self): if autospec is True: autospec = original + if _is_instance_mock(self.target): + raise InvalidSpecError( + f'Cannot autospec attr {self.attribute!r} as the patch ' + f'target has already been mocked out. ' + f'[target={self.target!r}, attr={autospec!r}]') + if _is_instance_mock(autospec): + target_name = getattr(self.target, '__name__', self.target) + raise InvalidSpecError( + f'Cannot autospec attr {self.attribute!r} from target ' + f'{target_name!r} as it has already been mocked out. ' + f'[target={self.target!r}, attr={autospec!r}]') + new = create_autospec(autospec, spec_set=spec_set, _name=self.attribute, **kwargs) elif kwargs: @@ -2635,6 +2667,9 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, spec = type(spec) is_type = isinstance(spec, type) + if _is_instance_mock(spec): + raise InvalidSpecError(f'Cannot autospec a Mock object. ' + f'[object={spec!r}]') is_async_func = _is_async_func(spec) _kwargs = {'spec': spec} if spec_set: diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index 9fd2b656..6419dd20 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -212,9 +212,9 @@ def test_create_autospec_instance(self): with self.assertRaises(RuntimeError): create_autospec(async_func, instance=True) + @unittest.skip('Broken test from https://bugs.python.org/issue37251') def test_create_autospec_awaitable_class(self): - awaitable_mock = create_autospec(spec=AwaitableClass()) - self.assertIsInstance(create_autospec(awaitable_mock), AsyncMock) + self.assertIsInstance(create_autospec(AwaitableClass), AsyncMock) def test_create_autospec(self): spec = create_autospec(async_func_args) diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 0fd14d90..ce5f37e6 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -12,7 +12,7 @@ NonCallableMagicMock, AsyncMock, create_autospec, mock ) -from mock.mock import _Call, _CallList +from mock.mock import _Call, _CallList, InvalidSpecError import mock.mock as mock_module @@ -206,6 +206,28 @@ def f(): pass self.assertRaisesRegex(ValueError, 'Bazinga!', mock) + def test_autospec_mock(self): + class A(object): + class B(object): + C = None + + with mock.patch.object(A, 'B'): + with self.assertRaisesRegex(InvalidSpecError, + "Cannot autospec attr 'B' from target Date: Wed, 28 Dec 2022 11:07:38 +0000 Subject: [PATCH 276/388] allow a specified rev to be backported. --- backport.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/backport.py b/backport.py index ce3841fe..2def1d90 100644 --- a/backport.py +++ b/backport.py @@ -129,7 +129,11 @@ def main(): cleanup_old_patches(args.mock) - revs = cpython_revs_affecting_mock(args.cpython, initial_cpython_rev) + if args.rev: + revs = [args.rev] + else: + revs = cpython_revs_affecting_mock(args.cpython, initial_cpython_rev) + for rev in revs: if has_been_backported(args.mock, rev): @@ -142,7 +146,8 @@ def main(): break else: - commit_last_sync(revs, args.mock) + if not args.rev: + commit_last_sync(revs, args.mock) def parse_args(): @@ -150,6 +155,7 @@ def parse_args(): parser.add_argument('--cpython', default='../cpython') parser.add_argument('--mock', default=abspath(dirname(__file__))) parser.add_argument('--list', action='store_true', help='list revs remaining to backport') + parser.add_argument('--rev', help='backport a specific git hash') parser.add_argument('--skip-current', action='store_true') parser.add_argument('--skip-reason', default='it has no changes needed here.') return parser.parse_args() From a88d686249ef37df2f14bdc576caf72fd535d6e3 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 28 Dec 2022 10:55:50 +0000 Subject: [PATCH 277/388] Remove skipped test in test for async mocks. (#100559) Remove skipped test. See discussion on https://github.com/python/cpython/pull/25326. Fix is apparently here, but no-one is confident to review and land: https://github.com/python/cpython/pull/25347. Backports: 984894a9a25c0f8298565b0c0c2e1f41917e4f88 Signed-off-by: Chris Withers --- mock/tests/testasync.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index 6419dd20..b1292ab0 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -212,10 +212,6 @@ def test_create_autospec_instance(self): with self.assertRaises(RuntimeError): create_autospec(async_func, instance=True) - @unittest.skip('Broken test from https://bugs.python.org/issue37251') - def test_create_autospec_awaitable_class(self): - self.assertIsInstance(create_autospec(AwaitableClass), AsyncMock) - def test_create_autospec(self): spec = create_autospec(async_func_args) awaitable = spec(1, 2, c=3) From cf0897695b1c6acd47469893ec5d3c1d065b3c17 Mon Sep 17 00:00:00 2001 From: Dong-hee Na Date: Thu, 6 May 2021 23:10:52 +0900 Subject: [PATCH 278/388] bpo-44017: Update test_contextlib_async not to emit DeprecationWarn (GH-25918) Backports: 698e9a8211c46ed5dc93e5cd7026ea05dec2f373 Signed-off-by: Chris Withers --- mock/tests/testasync.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index b1292ab0..f2ee4046 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -186,8 +186,7 @@ def foo(): pass def test_future_isfuture(self): loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - fut = asyncio.Future() + fut = loop.create_future() loop.stop() loop.close() mock = AsyncMock(fut) From 673d53f0ffda79c6d6ea0d7b82b6f79a320ba9fa Mon Sep 17 00:00:00 2001 From: Binbin Date: Sun, 13 Jun 2021 10:47:44 +0800 Subject: [PATCH 279/388] Fix typos in multiple files (GH-26689) Co-authored-by: Terry Jan Reedy Backports: 17b16e13bb444001534ed6fccb459084596c8bcf Signed-off-by: Chris Withers --- mock/mock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock/mock.py b/mock/mock.py index 17b133be..db17951c 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2221,7 +2221,7 @@ async def _execute_mock_call(_mock_self, *args, **kwargs): try: result = next(effect) except StopIteration: - # It is impossible to propogate a StopIteration + # It is impossible to propagate a StopIteration # through coroutines because of PEP 479 raise StopAsyncIteration if _is_exception(result): From 9e25d1d40eb79432c92f3120b4280e037855f845 Mon Sep 17 00:00:00 2001 From: Jack DeVries <58614260+jdevries3133@users.noreply.github.com> Date: Mon, 5 Jul 2021 02:52:32 -0400 Subject: [PATCH 280/388] bpo-44534: fix wording and docstring sync in unittest.Mock GH27000 Backports: abb08e3af6aa19928007a349592e95e6de38467f Signed-off-by: Chris Withers --- mock/mock.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mock/mock.py b/mock/mock.py index db17951c..31d4d385 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1225,6 +1225,11 @@ class or instance) that acts as the specification for the mock object. If this is a new Mock (created on first access). See the `return_value` attribute. + * `unsafe`: By default, accessing any attribute whose name starts with + *assert*, *assret*, *asert*, *aseert* or *assrt* will raise an + AttributeError. Passing `unsafe=True` will allow access to + these attributes. + * `wraps`: Item for the mock object to wrap. If `wraps` is not None then calling the Mock will pass the call through to the wrapped object (returning the real result). Attribute access on the mock will return a From f4c8dc7098abb6b2f9a65ee86bad3891776abb50 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 21 Jul 2021 12:47:44 +0100 Subject: [PATCH 281/388] bpo-44686 replace unittest.mock._importer with pkgutil.resolve_name (GH-18544) Automerge-Triggered-By: GH:cjw296 Backports: ab7fcc8fbdc11091370deeb000a787fb02f9b13d Signed-off-by: Chris Withers --- .../2021-07-20-19-35-49.bpo-44686.ucCGhu.rst | 1 + mock/mock.py | 27 +++---------------- 2 files changed, 5 insertions(+), 23 deletions(-) create mode 100644 NEWS.d/2021-07-20-19-35-49.bpo-44686.ucCGhu.rst diff --git a/NEWS.d/2021-07-20-19-35-49.bpo-44686.ucCGhu.rst b/NEWS.d/2021-07-20-19-35-49.bpo-44686.ucCGhu.rst new file mode 100644 index 00000000..d9c78020 --- /dev/null +++ b/NEWS.d/2021-07-20-19-35-49.bpo-44686.ucCGhu.rst @@ -0,0 +1 @@ +Replace ``unittest.mock._importer`` with ``pkgutil.resolve_name``. diff --git a/mock/mock.py b/mock/mock.py index 31d4d385..c9c50360 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -30,6 +30,7 @@ import pprint import sys import builtins +import pkgutil from asyncio import iscoroutinefunction from types import CodeType, ModuleType, MethodType from unittest.util import safe_repr @@ -1249,25 +1250,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) - except AttributeError: - __import__(import_path) - return getattr(thing, comp) - - -def _importer(target): - components = target.split('.') - import_path = components.pop(0) - thing = __import__(import_path) - - for comp in components: - import_path += ".%s" % comp - thing = _dot_lookup(thing, comp, import_path) - return thing - - # _check_spec_arg_typos takes kwargs from commands like patch and checks that # they don't contain common misspellings of arguments related to autospeccing. def _check_spec_arg_typos(kwargs_to_check): @@ -1621,8 +1603,7 @@ def _get_target(target): except (TypeError, ValueError): raise TypeError("Need a valid target to patch. You supplied: %r" % (target,)) - getter = lambda: _importer(target) - return getter, attribute + return partial(pkgutil.resolve_name, target), attribute def _patch_object( @@ -1677,7 +1658,7 @@ def _patch_multiple(target, spec=None, create=False, spec_set=None, for choosing which methods to wrap. """ if type(target) is str: - getter = lambda: _importer(target) + getter = partial(pkgutil.resolve_name, target) else: getter = lambda: target @@ -1857,7 +1838,7 @@ def __enter__(self): def _patch_dict(self): values = self.values if isinstance(self.in_dict, str): - self.in_dict = _importer(self.in_dict) + self.in_dict = pkgutil.resolve_name(self.in_dict) in_dict = self.in_dict clear = self.clear From f0bc27e3c69651c3159e827e7c972d2eef5e718a Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 28 Dec 2022 11:17:27 +0000 Subject: [PATCH 282/388] cpython master -> main branch name change. --- docs/index.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/index.txt b/docs/index.txt index 95f6817a..ac346946 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -143,7 +143,8 @@ Backporting process git clone https://github.com/python/cpython.git git clone https://github.com/testing-cabal/mock.git - Make sure they are both on master and up to date! + Make sure ``cpython` is on ``main` and that ``main`` branch is fully up to date! + Make sure ``mock`` is on master and up fully up to date! 2. Create a branch in your ``mock`` clone and switch to it. From eef67fa0387392584820bba77933b8c721945f57 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 28 Dec 2022 11:18:08 +0000 Subject: [PATCH 283/388] latest sync point --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index 6392a86c..0cce0a74 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -fdb9efce6ac211f973088eef508740c3fa2bd182 +abb08e3af6aa19928007a349592e95e6de38467f From bc04ea76352c2064d79160f13649f879667a89cb Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 28 Dec 2022 11:19:49 +0000 Subject: [PATCH 284/388] Revert "bpo-44686 replace unittest.mock._importer with pkgutil.resolve_name (GH-18544)" This reverts commit af2a3d6def15155946ffe6416229a89a29f4eb06. This change can be brought back once Python 3.10 is the oldest supported version. --- .../2021-07-20-19-35-49.bpo-44686.ucCGhu.rst | 1 - mock/mock.py | 27 ++++++++++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) delete mode 100644 NEWS.d/2021-07-20-19-35-49.bpo-44686.ucCGhu.rst diff --git a/NEWS.d/2021-07-20-19-35-49.bpo-44686.ucCGhu.rst b/NEWS.d/2021-07-20-19-35-49.bpo-44686.ucCGhu.rst deleted file mode 100644 index d9c78020..00000000 --- a/NEWS.d/2021-07-20-19-35-49.bpo-44686.ucCGhu.rst +++ /dev/null @@ -1 +0,0 @@ -Replace ``unittest.mock._importer`` with ``pkgutil.resolve_name``. diff --git a/mock/mock.py b/mock/mock.py index c9c50360..31d4d385 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -30,7 +30,6 @@ import pprint import sys import builtins -import pkgutil from asyncio import iscoroutinefunction from types import CodeType, ModuleType, MethodType from unittest.util import safe_repr @@ -1250,6 +1249,25 @@ 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) + except AttributeError: + __import__(import_path) + return getattr(thing, comp) + + +def _importer(target): + components = target.split('.') + import_path = components.pop(0) + thing = __import__(import_path) + + for comp in components: + import_path += ".%s" % comp + thing = _dot_lookup(thing, comp, import_path) + return thing + + # _check_spec_arg_typos takes kwargs from commands like patch and checks that # they don't contain common misspellings of arguments related to autospeccing. def _check_spec_arg_typos(kwargs_to_check): @@ -1603,7 +1621,8 @@ def _get_target(target): except (TypeError, ValueError): raise TypeError("Need a valid target to patch. You supplied: %r" % (target,)) - return partial(pkgutil.resolve_name, target), attribute + getter = lambda: _importer(target) + return getter, attribute def _patch_object( @@ -1658,7 +1677,7 @@ def _patch_multiple(target, spec=None, create=False, spec_set=None, for choosing which methods to wrap. """ if type(target) is str: - getter = partial(pkgutil.resolve_name, target) + getter = lambda: _importer(target) else: getter = lambda: target @@ -1838,7 +1857,7 @@ def __enter__(self): def _patch_dict(self): values = self.values if isinstance(self.in_dict, str): - self.in_dict = pkgutil.resolve_name(self.in_dict) + self.in_dict = _importer(self.in_dict) in_dict = self.in_dict clear = self.clear From e0747e0effcee4c11719a358073e9ae7198a6445 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 28 Dec 2022 11:24:46 +0000 Subject: [PATCH 285/388] Note about skip/reverting patches after they've been backported. --- docs/index.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/index.txt b/docs/index.txt index ac346946..a3a7eb78 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -131,6 +131,13 @@ Backporting rules def will_never_be_called(): yield +- If a backported patch applies cleanly, but ends up needing to be skipped, + then commit the latest sync point and then revert the problematic commit in an immediately + subsequent commit and make a not of the reason for the revert in that commit message. + + See ``1140c2930`` for an example where ``af2a3d6def15`` broke compatibility for all Python + versions earlier than 3.10. + Backporting process ------------------- From 3685f84ad62bc1da5864e6cb6025c9af8ed14c65 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 26 Aug 2021 21:19:47 +0300 Subject: [PATCH 286/388] bpo-45010: Remove support of special method __div__ in unittest.mock (GH-27965) Backports: f9cd40f5e242d3c64cc20a5064500f5fe864f91f Signed-off-by: Chris Withers --- NEWS.d/2021-08-26-09-54-14.bpo-45010.Cn23bQ.rst | 2 ++ lastsync.txt | 2 +- mock/mock.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 NEWS.d/2021-08-26-09-54-14.bpo-45010.Cn23bQ.rst diff --git a/NEWS.d/2021-08-26-09-54-14.bpo-45010.Cn23bQ.rst b/NEWS.d/2021-08-26-09-54-14.bpo-45010.Cn23bQ.rst new file mode 100644 index 00000000..bdf1bfe1 --- /dev/null +++ b/NEWS.d/2021-08-26-09-54-14.bpo-45010.Cn23bQ.rst @@ -0,0 +1,2 @@ +Remove support of special method ``__div__`` in :mod:`unittest.mock`. It is +not used in Python 3. diff --git a/lastsync.txt b/lastsync.txt index 0cce0a74..cdaa692a 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -abb08e3af6aa19928007a349592e95e6de38467f +ab7fcc8fbdc11091370deeb000a787fb02f9b13d diff --git a/mock/mock.py b/mock/mock.py index 31d4d385..4fe1bffe 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1962,7 +1962,7 @@ def _patch_stopall(): magic_methods = magic_methods.replace('sizeof ', '') numerics = ( - "add sub mul matmul div floordiv mod lshift rshift and xor or pow truediv" + "add sub mul matmul truediv floordiv mod lshift rshift and xor or pow" ) inplace = ' '.join('i%s' % n for n in numerics.split()) right = ' '.join('r%s' % n for n in numerics.split()) From ea6f2fbc820694e45dceb585c7ae9dcb2cb124a2 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Tue, 14 Sep 2021 13:20:40 +0300 Subject: [PATCH 287/388] bpo-45156: Fixes inifite loop on unittest.mock.seal() (GH-28300) Fixes infinite loop on unittest.mock.seal() of mocks created by unittest.create_autospec(). Co-authored-by: Dong-hee Na Backports: 7f60c9e1c6e22cc0e846a872c318570926cd3094 Signed-off-by: Chris Withers --- .../2021-09-13-00-28-17.bpo-45156.8oomV3.rst | 2 + mock/mock.py | 13 ++-- mock/tests/testsealable.py | 61 +++++++++++++++++++ 3 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 NEWS.d/2021-09-13-00-28-17.bpo-45156.8oomV3.rst diff --git a/NEWS.d/2021-09-13-00-28-17.bpo-45156.8oomV3.rst b/NEWS.d/2021-09-13-00-28-17.bpo-45156.8oomV3.rst new file mode 100644 index 00000000..b2094b57 --- /dev/null +++ b/NEWS.d/2021-09-13-00-28-17.bpo-45156.8oomV3.rst @@ -0,0 +1,2 @@ +Fixes infinite loop on :func:`unittest.mock.seal` of mocks created by +:func:`~unittest.create_autospec`. diff --git a/mock/mock.py b/mock/mock.py index 4fe1bffe..d34e4c4e 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1012,6 +1012,11 @@ def _get_child_mock(self, **kw): if _new_name in self.__dict__['_spec_asyncs']: return AsyncMock(**kw) + if self._mock_sealed: + attribute = f".{kw['name']}" if "name" in kw else "()" + mock_name = self._extract_mock_name() + attribute + raise AttributeError(mock_name) + _type = type(self) if issubclass(_type, MagicMock) and _new_name in _async_method_magics: # Any asynchronous magic becomes an AsyncMock @@ -1030,12 +1035,6 @@ def _get_child_mock(self, **kw): klass = Mock else: klass = _type.__mro__[1] - - if self._mock_sealed: - attribute = "." + kw["name"] if "name" in kw else "()" - mock_name = self._extract_mock_name() + attribute - raise AttributeError(mock_name) - return klass(**kw) @@ -2955,6 +2954,8 @@ def seal(mock): continue if not isinstance(m, NonCallableMock): continue + if isinstance(m._mock_children.get(attr), _SpecState): + continue if m._mock_new_parent is mock: seal(m) diff --git a/mock/tests/testsealable.py b/mock/tests/testsealable.py index 63a85414..28701fa4 100644 --- a/mock/tests/testsealable.py +++ b/mock/tests/testsealable.py @@ -171,6 +171,67 @@ def test_call_chain_is_maintained(self): m.test1().test2.test3().test4() self.assertIn("mock.test1().test2.test3().test4", str(cm.exception)) + def test_seal_with_autospec(self): + # https://bugs.python.org/issue45156 + class Foo: + foo = 0 + def bar1(self): + return 1 + def bar2(self): + return 2 + + class Baz: + baz = 3 + def ban(self): + return 4 + + for spec_set in (True, False): + with self.subTest(spec_set=spec_set): + foo = mock.create_autospec(Foo, spec_set=spec_set) + foo.bar1.return_value = 'a' + foo.Baz.ban.return_value = 'b' + + mock.seal(foo) + + self.assertIsInstance(foo.foo, mock.NonCallableMagicMock) + self.assertIsInstance(foo.bar1, mock.MagicMock) + self.assertIsInstance(foo.bar2, mock.MagicMock) + self.assertIsInstance(foo.Baz, mock.MagicMock) + self.assertIsInstance(foo.Baz.baz, mock.NonCallableMagicMock) + self.assertIsInstance(foo.Baz.ban, mock.MagicMock) + + self.assertEqual(foo.bar1(), 'a') + foo.bar1.return_value = 'new_a' + self.assertEqual(foo.bar1(), 'new_a') + self.assertEqual(foo.Baz.ban(), 'b') + foo.Baz.ban.return_value = 'new_b' + self.assertEqual(foo.Baz.ban(), 'new_b') + + with self.assertRaises(TypeError): + foo.foo() + with self.assertRaises(AttributeError): + foo.bar = 1 + with self.assertRaises(AttributeError): + foo.bar2() + + foo.bar2.return_value = 'bar2' + self.assertEqual(foo.bar2(), 'bar2') + + with self.assertRaises(AttributeError): + foo.missing_attr + with self.assertRaises(AttributeError): + foo.missing_attr = 1 + with self.assertRaises(AttributeError): + foo.missing_method() + with self.assertRaises(TypeError): + foo.Baz.baz() + with self.assertRaises(AttributeError): + foo.Baz.missing_attr + with self.assertRaises(AttributeError): + foo.Baz.missing_attr = 1 + with self.assertRaises(AttributeError): + foo.Baz.missing_method() + if __name__ == "__main__": unittest.main() From 4958fc3d8876b9d70e180b17ed67b05eb0c5f7b3 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 7 Oct 2021 01:13:48 +0200 Subject: [PATCH 288/388] Fix typos in the Lib directory (GH-28775) Fix typos in the Lib directory as identified by codespell. Co-authored-by: Terry Jan Reedy Backports: 745c9d9dfc1ad6fdfdf1d07420c6273ff67fa5be Signed-off-by: Chris Withers --- mock/tests/testsealable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock/tests/testsealable.py b/mock/tests/testsealable.py index 28701fa4..8a23ede4 100644 --- a/mock/tests/testsealable.py +++ b/mock/tests/testsealable.py @@ -128,7 +128,7 @@ def test_integration_with_spec_att_definition(self): m.attr_sample2 def test_integration_with_spec_method_definition(self): - """You need to defin the methods, even if they are in the spec""" + """You need to define the methods, even if they are in the spec""" m = mock.Mock(SampleObject) m.method_sample1.return_value = 1 From ca8182c74b8ee8c51be2f6f920efbebb3f49d4e8 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 17 Dec 2021 11:10:55 +0200 Subject: [PATCH 289/388] bpo-46111: Fix unittest tests in optimized mode (GH-30163) Backports: 95a922b3bb3af247ec141d73fcdfbf68bb1d32a5 Signed-off-by: Chris Withers --- mock/tests/testpatch.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index 070d7e81..e7ebe841 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -1875,9 +1875,10 @@ def foo(x=0): self.assertEqual(foo(), 1) self.assertEqual(foo(), 0) + orig_doc = foo.__doc__ with patch.object(foo, '__doc__', "FUN"): self.assertEqual(foo.__doc__, "FUN") - self.assertEqual(foo.__doc__, "TEST") + self.assertEqual(foo.__doc__, orig_doc) with patch.object(foo, '__module__', "testpatch2"): self.assertEqual(foo.__module__, "testpatch2") From 4021557356699997bbdb8e069649305115424bc5 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Sun, 23 Jan 2022 18:42:41 +0000 Subject: [PATCH 290/388] bpo-41403: Improve error message for invalid mock target (GH-30833) Backports: f7955a82e36d4c32ebdd7b7707cdf0e6ffa7a418 Signed-off-by: Chris Withers --- NEWS.d/2022-01-23-18-04-45.bpo-41403.SgoHqV.rst | 3 +++ mock/mock.py | 6 +++--- mock/tests/testpatch.py | 9 +++++++-- 3 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 NEWS.d/2022-01-23-18-04-45.bpo-41403.SgoHqV.rst diff --git a/NEWS.d/2022-01-23-18-04-45.bpo-41403.SgoHqV.rst b/NEWS.d/2022-01-23-18-04-45.bpo-41403.SgoHqV.rst new file mode 100644 index 00000000..ede159b2 --- /dev/null +++ b/NEWS.d/2022-01-23-18-04-45.bpo-41403.SgoHqV.rst @@ -0,0 +1,3 @@ +Make :meth:`mock.patch` raise a :exc:`TypeError` with a relevant error +message on invalid arg. Previously it allowed a cryptic +:exc:`AttributeError` to escape. diff --git a/mock/mock.py b/mock/mock.py index d34e4c4e..2f64ea4f 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1617,9 +1617,9 @@ def stop(self): def _get_target(target): try: target, attribute = target.rsplit('.', 1) - except (TypeError, ValueError): - raise TypeError("Need a valid target to patch. You supplied: %r" % - (target,)) + except (TypeError, ValueError, AttributeError): + raise TypeError( + f"Need a valid target to patch. You supplied: {target!r}") getter = lambda: _importer(target) return getter, attribute diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index e7ebe841..3e4c29e0 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -1933,8 +1933,13 @@ def test(mock): def test_invalid_target(self): - with self.assertRaises(TypeError): - patch('') + class Foo: + pass + + for target in ['', 12, Foo()]: + with self.subTest(target=target): + with self.assertRaises(TypeError): + patch(target) def test_cant_set_kwargs_when_passing_a_mock(self): From 41572bc9b5ba4805e0096eb19dfbeb55d2a08db5 Mon Sep 17 00:00:00 2001 From: Matthew Suozzo Date: Thu, 3 Feb 2022 03:41:19 -0500 Subject: [PATCH 291/388] Restrict use of Mock objects as specs (GH-31090) Follow-on to https://github.com/python/cpython/pull/25326 This covers cases where mock objects are passed directly to spec. Backports: 6394e981adaca2c0daa36c8701611e250d74024c Signed-off-by: Chris Withers --- NEWS.d/2022-02-03-00-21-32.bpo-43478.0nfcam.rst | 1 + mock/mock.py | 10 +++++++++- mock/tests/testmock.py | 8 ++++++++ mock/tests/testwith.py | 4 ++-- 4 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 NEWS.d/2022-02-03-00-21-32.bpo-43478.0nfcam.rst diff --git a/NEWS.d/2022-02-03-00-21-32.bpo-43478.0nfcam.rst b/NEWS.d/2022-02-03-00-21-32.bpo-43478.0nfcam.rst new file mode 100644 index 00000000..7c8fc47c --- /dev/null +++ b/NEWS.d/2022-02-03-00-21-32.bpo-43478.0nfcam.rst @@ -0,0 +1 @@ +Mocks can no longer be provided as the specs for other Mocks. As a result, an already-mocked object cannot be passed to `mock.Mock()`. This can uncover bugs in tests since these Mock-derived Mocks will always pass certain tests (e.g. isinstance) and builtin assert functions (e.g. assert_called_once_with) will unconditionally pass. diff --git a/mock/mock.py b/mock/mock.py index 2f64ea4f..daf86818 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -491,6 +491,9 @@ def mock_add_spec(self, spec, spec_set=False): def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False, _eat_self=False): + if _is_instance_mock(spec): + raise InvalidSpecError(f'Cannot spec a Mock object. [object={spec!r}]') + _spec_class = None _spec_signature = None _spec_asyncs = [] @@ -2831,6 +2834,7 @@ def __init__(self, spec, spec_set=False, parent=None, file_spec = None +open_spec = None def _to_stream(read_data): @@ -2887,8 +2891,12 @@ def _next_side_effect(): import _io file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO)))) + global open_spec + if open_spec is None: + import _io + open_spec = list(set(dir(_io.open))) if mock is None: - mock = MagicMock(name='open', spec=open) + mock = MagicMock(name='open', spec=open_spec) handle = MagicMock(spec=file_spec) handle.__enter__.return_value = handle diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index ce5f37e6..e00baf9a 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -227,6 +227,14 @@ class B(object): with self.assertRaisesRegex(InvalidSpecError, "Cannot spec attr 'B' as the spec_set "): mock.patch.object(A, 'B', spec_set=A.B).start() + with self.assertRaisesRegex(InvalidSpecError, + "Cannot spec attr 'B' as the spec_set "): + mock.patch.object(A, 'B', spec_set=A.B).start() + with self.assertRaisesRegex(InvalidSpecError, "Cannot spec a Mock object."): + mock.Mock(A.B) + with mock.patch('builtins.open', mock.mock_open()): + mock.mock_open() # should still be valid with open() mocked + def test_reset_mock(self): parent = Mock() diff --git a/mock/tests/testwith.py b/mock/tests/testwith.py index 825387b7..34326f5d 100644 --- a/mock/tests/testwith.py +++ b/mock/tests/testwith.py @@ -130,8 +130,8 @@ def f(self): pass c = C() - with patch.object(c, 'f', autospec=True) as patch1: - with patch.object(c, 'f', autospec=True) as patch2: + with patch.object(c, 'f') as patch1: + with patch.object(c, 'f') as patch2: c.f() self.assertEqual(patch2.call_count, 1) self.assertEqual(patch1.call_count, 0) From 807c10d98845b9f638a45e68fe4764ed69a7dfda Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sat, 26 Feb 2022 00:53:27 +0100 Subject: [PATCH 292/388] bpo-46852: Remove the float.__set_format__() method (GH-31585) Remove the undocumented private float.__set_format__() method, previously known as float.__set_format__() in Python 3.7. Its docstring said: "You probably don't want to use this function. It exists mainly to be used in Python's test suite." Backports: 5ab745fc51e159ead28b523414e52f0bcc1ef353 Signed-off-by: Chris Withers --- NEWS.d/2022-02-25-01-42-45.bpo-46852.nkRDvV.rst | 4 ++++ mock/mock.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 NEWS.d/2022-02-25-01-42-45.bpo-46852.nkRDvV.rst diff --git a/NEWS.d/2022-02-25-01-42-45.bpo-46852.nkRDvV.rst b/NEWS.d/2022-02-25-01-42-45.bpo-46852.nkRDvV.rst new file mode 100644 index 00000000..cd0049a4 --- /dev/null +++ b/NEWS.d/2022-02-25-01-42-45.bpo-46852.nkRDvV.rst @@ -0,0 +1,4 @@ +Remove the undocumented private ``float.__set_format__()`` method, previously +known as ``float.__setformat__()`` in Python 3.7. Its docstring said: "You +probably don't want to use this function. It exists mainly to be used in +Python's test suite." Patch by Victor Stinner. diff --git a/mock/mock.py b/mock/mock.py index daf86818..b694e1c6 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1976,7 +1976,7 @@ def _patch_stopall(): _non_defaults = { '__get__', '__set__', '__delete__', '__reversed__', '__missing__', '__reduce__', '__reduce_ex__', '__getinitargs__', '__getnewargs__', - '__getstate__', '__setstate__', '__getformat__', '__setformat__', + '__getstate__', '__setstate__', '__getformat__', '__repr__', '__dir__', '__subclasses__', '__format__', '__getnewargs_ex__', } From ff13cce8e2693f7f1ad8bb0ad86a32c0668e5374 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 22 Mar 2022 12:04:36 +0200 Subject: [PATCH 293/388] bpo-40280: Skip socket, fork, subprocess tests on Emscripten (GH-31986) - Add requires_fork and requires_subprocess to more tests - Skip extension import tests if dlopen is not available - Don't assume that _testcapi is a shared extension - Skip a lot of socket tests that don't work on Emscripten - Skip mmap tests, mmap emulation is incomplete - venv does not work yet - Cannot get libc from executable The "entire" test suite is now passing on Emscripten with EMSDK from git head (91 suites are skipped). Backports: deeaac49e267285158264643799624623f4a7b29 Signed-off-by: Chris Withers --- NEWS.d/2022-03-19-10-25-04.bpo-40280.wBRSel.rst | 2 ++ mock/tests/testasync.py | 4 ++++ 2 files changed, 6 insertions(+) create mode 100644 NEWS.d/2022-03-19-10-25-04.bpo-40280.wBRSel.rst diff --git a/NEWS.d/2022-03-19-10-25-04.bpo-40280.wBRSel.rst b/NEWS.d/2022-03-19-10-25-04.bpo-40280.wBRSel.rst new file mode 100644 index 00000000..2572c27a --- /dev/null +++ b/NEWS.d/2022-03-19-10-25-04.bpo-40280.wBRSel.rst @@ -0,0 +1,2 @@ +The test suite is now passing on the Emscripten platform. All fork, socket, +and subprocess-based tests are skipped. diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index f2ee4046..923e7376 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -5,6 +5,10 @@ import re import unittest from contextlib import contextmanager +from test import support + +support.requires_working_socket(module=True) + from mock import (ANY, call, AsyncMock, patch, MagicMock, Mock, create_autospec, sentinel) From 7179eefb64feddf05f54cd6cd9ec742032849de7 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 28 Dec 2022 11:42:51 +0000 Subject: [PATCH 294/388] Revert "bpo-40280: Skip socket, fork, subprocess tests on Emscripten (GH-31986)" This reverts commit b87c00d2158b32140ef7664eda596b6210d3a326. Backport doesn't currently support Emscripten. --- NEWS.d/2022-03-19-10-25-04.bpo-40280.wBRSel.rst | 2 -- mock/tests/testasync.py | 4 ---- 2 files changed, 6 deletions(-) delete mode 100644 NEWS.d/2022-03-19-10-25-04.bpo-40280.wBRSel.rst diff --git a/NEWS.d/2022-03-19-10-25-04.bpo-40280.wBRSel.rst b/NEWS.d/2022-03-19-10-25-04.bpo-40280.wBRSel.rst deleted file mode 100644 index 2572c27a..00000000 --- a/NEWS.d/2022-03-19-10-25-04.bpo-40280.wBRSel.rst +++ /dev/null @@ -1,2 +0,0 @@ -The test suite is now passing on the Emscripten platform. All fork, socket, -and subprocess-based tests are skipped. diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index 923e7376..f2ee4046 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -5,10 +5,6 @@ import re import unittest from contextlib import contextmanager -from test import support - -support.requires_working_socket(module=True) - from mock import (ANY, call, AsyncMock, patch, MagicMock, Mock, create_autospec, sentinel) From c3a1bf7a61a316777ee9292dfda3b4262d0d1d64 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 28 Dec 2022 11:43:41 +0000 Subject: [PATCH 295/388] Backports: c735d545343c3ab002c62596b2fb2cfa4488b0af, skipped: it has no changes needed here. --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index cdaa692a..0b0889a9 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -ab7fcc8fbdc11091370deeb000a787fb02f9b13d +c735d545343c3ab002c62596b2fb2cfa4488b0af From 5e587393b9986019b15c454d8a99b7624cdd779a Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 28 Dec 2022 11:44:26 +0000 Subject: [PATCH 296/388] Backports: 50ebd72fb0e69c78f95cea3d4a47589beb91ac37, skipped: backport uses pytest. --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index 0b0889a9..27cf75f3 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -c735d545343c3ab002c62596b2fb2cfa4488b0af +50ebd72fb0e69c78f95cea3d4a47589beb91ac37 From 4ce685a531d98ae1183e1378577ce57031e9706d Mon Sep 17 00:00:00 2001 From: Mehdi ABAAKOUK Date: Thu, 30 Jun 2022 19:08:38 +0200 Subject: [PATCH 297/388] gh-84753: Make inspect.iscoroutinefunction() work with AsyncMock (#94050) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The inspect version was not working with unittest.mock.AsyncMock. The fix introduces special-casing of AsyncMock in `inspect.iscoroutinefunction` equivalent to the one performed in `asyncio.iscoroutinefunction`. Co-authored-by: Łukasz Langa Backports: 4261b6bffc0b8bb5c6d4d80578a81b7520f4aefc Signed-off-by: Chris Withers --- NEWS.d/2022-06-21-11-40-31.gh-issue-84753.FW1pxO.rst | 3 +++ mock/mock.py | 4 ++++ 2 files changed, 7 insertions(+) create mode 100644 NEWS.d/2022-06-21-11-40-31.gh-issue-84753.FW1pxO.rst diff --git a/NEWS.d/2022-06-21-11-40-31.gh-issue-84753.FW1pxO.rst b/NEWS.d/2022-06-21-11-40-31.gh-issue-84753.FW1pxO.rst new file mode 100644 index 00000000..f701d2a1 --- /dev/null +++ b/NEWS.d/2022-06-21-11-40-31.gh-issue-84753.FW1pxO.rst @@ -0,0 +1,3 @@ +:func:`inspect.iscoroutinefunction` now properly returns ``True`` when an instance +of :class:`unittest.mock.AsyncMock` is passed to it. This makes it consistent with +behavior of :func:`asyncio.iscoroutinefunction`. Patch by Mehdi ABAAKOUK. diff --git a/mock/mock.py b/mock/mock.py index b694e1c6..24457b02 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2209,6 +2209,10 @@ def __init__(self, *args, **kwargs): code_mock = NonCallableMock(spec_set=CodeType) code_mock.co_flags = inspect.CO_COROUTINE self.__dict__['__code__'] = code_mock + self.__dict__['__name__'] = 'AsyncMock' + self.__dict__['__defaults__'] = tuple() + self.__dict__['__kwdefaults__'] = {} + self.__dict__['__annotations__'] = None async def _execute_mock_call(_mock_self, *args, **kwargs): self = _mock_self From a8f85506778e04a84489209214bfa6d5f4a0a7a3 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 28 Dec 2022 11:51:43 +0000 Subject: [PATCH 298/388] Backports: 569ca27293eec89b5b41c3a12e6531d3eddf0e1b, skipped: package locations are different in backport --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index 27cf75f3..a2f4c0e5 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -50ebd72fb0e69c78f95cea3d4a47589beb91ac37 +569ca27293eec89b5b41c3a12e6531d3eddf0e1b From 2b647d6677fee5cd064b0aa2528cab061f6220bd Mon Sep 17 00:00:00 2001 From: noah-weingarden <33741795+noah-weingarden@users.noreply.github.com> Date: Fri, 28 Oct 2022 03:51:18 -0400 Subject: [PATCH 299/388] gh-98624 Add mutex to unittest.mock.NonCallableMock (#98688) * Added lock to NonCallableMock in unittest.mock * Add blurb * Nitpick blurb * Edit comment based on @Jason-Y-Z's review * Add link to GH issue Backports: 0346eddbe933b5f1f56151bdebf5bd49392bc275 Signed-off-by: Chris Withers --- ...2-10-25-20-17-34.gh-issue-98624.YQUPFy.rst | 2 + mock/mock.py | 67 +++++++++++-------- 2 files changed, 41 insertions(+), 28 deletions(-) create mode 100644 NEWS.d/2022-10-25-20-17-34.gh-issue-98624.YQUPFy.rst diff --git a/NEWS.d/2022-10-25-20-17-34.gh-issue-98624.YQUPFy.rst b/NEWS.d/2022-10-25-20-17-34.gh-issue-98624.YQUPFy.rst new file mode 100644 index 00000000..fb3a2b83 --- /dev/null +++ b/NEWS.d/2022-10-25-20-17-34.gh-issue-98624.YQUPFy.rst @@ -0,0 +1,2 @@ +Add a mutex to unittest.mock.NonCallableMock to protect concurrent access +to mock attributes. diff --git a/mock/mock.py b/mock/mock.py index 24457b02..cf0aea60 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -34,6 +34,8 @@ from types import CodeType, ModuleType, MethodType from unittest.util import safe_repr from functools import wraps, partial +from threading import RLock + from mock import IS_PYPY from .backports import iscoroutinefunction @@ -404,6 +406,14 @@ def __init__(self, *args, **kwargs): class NonCallableMock(Base): """A non-callable version of `Mock`""" + # Store a mutex as a class attribute in order to protect concurrent access + # to mock attributes. Using a class attribute allows all NonCallableMock + # instances to share the mutex for simplicity. + # + # See https://github.com/python/cpython/issues/98624 for why this is + # necessary. + _lock = RLock() + def __new__(cls, *args, **kw): # every instance has its own class # so we can create magic methods on the @@ -646,35 +656,36 @@ def __getattr__(self, name): f"{name!r} is not a valid assertion. Use a spec " f"for the mock if {name!r} is meant to be an attribute.") - result = self._mock_children.get(name) - if result is _deleted: - raise AttributeError(name) - elif result is None: - wraps = None - if self._mock_wraps is not None: - # XXXX should we get the attribute without triggering code - # execution? - wraps = getattr(self._mock_wraps, name) - - result = self._get_child_mock( - parent=self, name=name, wraps=wraps, _new_name=name, - _new_parent=self - ) - self._mock_children[name] = result - - elif isinstance(result, _SpecState): - try: - result = create_autospec( - result.spec, result.spec_set, result.instance, - result.parent, result.name + with NonCallableMock._lock: + result = self._mock_children.get(name) + if result is _deleted: + raise AttributeError(name) + elif result is None: + wraps = None + if self._mock_wraps is not None: + # XXXX should we get the attribute without triggering code + # execution? + wraps = getattr(self._mock_wraps, name) + + result = self._get_child_mock( + parent=self, name=name, wraps=wraps, _new_name=name, + _new_parent=self ) - except InvalidSpecError: - target_name = self.__dict__['_mock_name'] or self - raise InvalidSpecError( - f'Cannot autospec attr {name!r} from target ' - f'{target_name!r} as it has already been mocked out. ' - f'[target={self!r}, attr={result.spec!r}]') - self._mock_children[name] = result + self._mock_children[name] = result + + elif isinstance(result, _SpecState): + try: + result = create_autospec( + result.spec, result.spec_set, result.instance, + result.parent, result.name + ) + except InvalidSpecError: + target_name = self.__dict__['_mock_name'] or self + raise InvalidSpecError( + f'Cannot autospec attr {name!r} from target ' + f'{target_name!r} as it has already been mocked out. ' + f'[target={self!r}, attr={result.spec!r}]') + self._mock_children[name] = result return result From 4d3f1976a8e761f5f2a8a362bd86f6b6e108a7cf Mon Sep 17 00:00:00 2001 From: andrei kulakov Date: Mon, 7 Nov 2022 02:24:46 -0500 Subject: [PATCH 300/388] gh-91803: Mock - fix error when using autospec methods with seal (#92213) Fixes https://github.com/python/cpython/issues/91803. Co-authored-by: Karthikeyan Singaravelan Co-authored-by: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Backports: c6325b1c9fe60f72bb3fa4b8570a699e9e97af53 Signed-off-by: Chris Withers --- NEWS.d/2022-05-03-11-32-29.gh-issue-91803.pI4Juv.rst | 3 +++ mock/mock.py | 1 + mock/tests/testsealable.py | 5 ++++- 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 NEWS.d/2022-05-03-11-32-29.gh-issue-91803.pI4Juv.rst diff --git a/NEWS.d/2022-05-03-11-32-29.gh-issue-91803.pI4Juv.rst b/NEWS.d/2022-05-03-11-32-29.gh-issue-91803.pI4Juv.rst new file mode 100644 index 00000000..14829e8f --- /dev/null +++ b/NEWS.d/2022-05-03-11-32-29.gh-issue-91803.pI4Juv.rst @@ -0,0 +1,3 @@ +Fix an error when using a method of objects mocked with +:func:`unittest.mock.create_autospec` after it was sealed with +:func:`unittest.mock.seal` function. diff --git a/mock/mock.py b/mock/mock.py index cf0aea60..7f0e8584 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2788,6 +2788,7 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, _new_parent=parent, **kwargs) mock._mock_children[entry] = new + new.return_value = child_klass() _check_signature(original, new, skipfirst=skipfirst) # so functions created with _set_signature become instance attributes, diff --git a/mock/tests/testsealable.py b/mock/tests/testsealable.py index 8a23ede4..e7eaec88 100644 --- a/mock/tests/testsealable.py +++ b/mock/tests/testsealable.py @@ -200,6 +200,9 @@ def ban(self): self.assertIsInstance(foo.Baz.baz, mock.NonCallableMagicMock) self.assertIsInstance(foo.Baz.ban, mock.MagicMock) + # see gh-91803 + self.assertIsInstance(foo.bar2(), mock.MagicMock) + self.assertEqual(foo.bar1(), 'a') foo.bar1.return_value = 'new_a' self.assertEqual(foo.bar1(), 'new_a') @@ -212,7 +215,7 @@ def ban(self): with self.assertRaises(AttributeError): foo.bar = 1 with self.assertRaises(AttributeError): - foo.bar2() + foo.bar2().x foo.bar2.return_value = 'bar2' self.assertEqual(foo.bar2(), 'bar2') From a2cb0beb4b36dcd20d1cd3fa6fffb2c3e09fb893 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Fri, 11 Nov 2022 11:04:30 +0300 Subject: [PATCH 301/388] gh-98086: Now ``patch.dict`` can decorate async functions (#98095) Backports: 67b4d2772c5124b908f8ed9b13166a79bbeb88d2 Signed-off-by: Chris Withers --- ...22-10-08-19-39-27.gh-issue-98086.y---WC.rst | 1 + mock/mock.py | 18 ++++++++++++++++++ mock/tests/testasync.py | 17 +++++++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 NEWS.d/2022-10-08-19-39-27.gh-issue-98086.y---WC.rst diff --git a/NEWS.d/2022-10-08-19-39-27.gh-issue-98086.y---WC.rst b/NEWS.d/2022-10-08-19-39-27.gh-issue-98086.y---WC.rst new file mode 100644 index 00000000..f4a1d272 --- /dev/null +++ b/NEWS.d/2022-10-08-19-39-27.gh-issue-98086.y---WC.rst @@ -0,0 +1 @@ +Make sure ``patch.dict()`` can be applied on async functions. diff --git a/mock/mock.py b/mock/mock.py index 7f0e8584..95600b80 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1839,6 +1839,12 @@ def __init__(self, in_dict, values=(), clear=False, **kwargs): def __call__(self, f): if isinstance(f, type): return self.decorate_class(f) + if inspect.iscoroutinefunction(f): + return self.decorate_async_callable(f) + return self.decorate_callable(f) + + + def decorate_callable(self, f): @wraps(f) def _inner(*args, **kw): self._patch_dict() @@ -1850,6 +1856,18 @@ def _inner(*args, **kw): return _inner + def decorate_async_callable(self, f): + @wraps(f) + async def _inner(*args, **kw): + self._patch_dict() + try: + return await f(*args, **kw) + finally: + self._unpatch_dict() + + return _inner + + def decorate_class(self, klass): for attr in dir(klass): attr_value = getattr(klass, attr) diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index f2ee4046..d0e9c533 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -159,6 +159,23 @@ async def test_async(): run(test_async()) + def test_patch_dict_async_def(self): + foo = {'a': 'a'} + @patch.dict(foo, {'a': 'b'}) + async def test_async(): + self.assertEqual(foo['a'], 'b') + + self.assertTrue(iscoroutinefunction(test_async)) + run(test_async()) + + def test_patch_dict_async_def_context(self): + foo = {'a': 'a'} + async def test_async(): + with patch.dict(foo, {'a': 'b'}): + self.assertEqual(foo['a'], 'b') + + run(test_async()) + class AsyncMockTest(unittest.TestCase): def test_iscoroutinefunction_default(self): From 36e4a686a0f80fdcb4a8dee70343fd2aa07af241 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Fri, 23 Dec 2022 12:41:37 -0700 Subject: [PATCH 302/388] gh-83076: 3.8x speed improvement in (Async)Mock instantiation (#100252) Backports: c5726b727e26b81a267933654cf26b760a90d9aa Signed-off-by: Chris Withers --- ...2-12-14-17-37-01.gh-issue-83076.NaYzWT.rst | 1 + mock/mock.py | 37 +++++++++++-------- mock/tests/testasync.py | 13 +++++++ 3 files changed, 35 insertions(+), 16 deletions(-) create mode 100644 NEWS.d/2022-12-14-17-37-01.gh-issue-83076.NaYzWT.rst diff --git a/NEWS.d/2022-12-14-17-37-01.gh-issue-83076.NaYzWT.rst b/NEWS.d/2022-12-14-17-37-01.gh-issue-83076.NaYzWT.rst new file mode 100644 index 00000000..a4984e69 --- /dev/null +++ b/NEWS.d/2022-12-14-17-37-01.gh-issue-83076.NaYzWT.rst @@ -0,0 +1 @@ +Instantiation of ``Mock()`` and ``AsyncMock()`` is now 3.8x faster. diff --git a/mock/mock.py b/mock/mock.py index 95600b80..4a16ec0e 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -414,15 +414,18 @@ class NonCallableMock(Base): # necessary. _lock = RLock() - def __new__(cls, *args, **kw): + def __new__( + cls, spec=None, wraps=None, name=None, spec_set=None, + parent=None, _spec_state=None, _new_name='', _new_parent=None, + _spec_as_instance=False, _eat_self=None, unsafe=False, **kwargs + ): # every instance has its own class # so we can create magic methods on the # class without stomping on other mocks bases = (cls,) if not issubclass(cls, AsyncMockMixin): # Check if spec is an async object or function - bound_args = _MOCK_SIG.bind_partial(cls, *args, **kw).arguments - spec_arg = bound_args.get('spec_set', bound_args.get('spec')) + spec_arg = spec_set or spec if spec_arg is not None and _is_async_obj(spec_arg): bases = (AsyncMockMixin, cls) new = type(cls.__name__, bases, {'__doc__': cls.__doc__}) @@ -508,10 +511,6 @@ def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False, _spec_signature = None _spec_asyncs = [] - for attr in dir(spec): - if iscoroutinefunction(getattr(spec, attr, None)): - _spec_asyncs.append(attr) - if spec is not None and not _is_list(spec): if isinstance(spec, type): _spec_class = spec @@ -521,7 +520,13 @@ def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False, _spec_as_instance, _eat_self) _spec_signature = res and res[1] - spec = dir(spec) + spec_list = dir(spec) + + for attr in spec_list: + if iscoroutinefunction(getattr(spec, attr, None)): + _spec_asyncs.append(attr) + + spec = spec_list __dict__ = self.__dict__ __dict__['_spec_class'] = _spec_class @@ -1065,9 +1070,6 @@ 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 @@ -2172,10 +2174,7 @@ def mock_add_spec(self, spec, spec_set=False): class AsyncMagicMixin(MagicMixin): - def __init__(self, *args, **kw): - self._mock_set_magics() # make magic work for kwargs in init - _safe_super(AsyncMagicMixin, self).__init__(*args, **kw) - self._mock_set_magics() # fix magic broken by upper level init + pass class MagicMock(MagicMixin, Mock): @@ -2218,6 +2217,10 @@ def __get__(self, obj, _type=None): return self.create_mock() +_CODE_ATTRS = dir(CodeType) +_CODE_SIG = inspect.signature(partial(CodeType.__init__, None)) + + class AsyncMockMixin(Base): await_count = _delegating_property('await_count') await_args = _delegating_property('await_args') @@ -2235,7 +2238,9 @@ def __init__(self, *args, **kwargs): 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 = NonCallableMock(spec_set=_CODE_ATTRS) + code_mock.__dict__["_spec_class"] = CodeType + code_mock.__dict__["_spec_signature"] = _CODE_SIG code_mock.co_flags = inspect.CO_COROUTINE self.__dict__['__code__'] = code_mock self.__dict__['__name__'] = 'AsyncMock' diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index d0e9c533..8269e5fb 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -306,6 +306,19 @@ def test_spec_normal_methods_on_class_with_mock(self): self.assertIsInstance(mock.async_method, AsyncMock) self.assertIsInstance(mock.normal_method, Mock) + def test_spec_async_attributes_instance(self): + async_instance = AsyncClass() + async_instance.async_func_attr = async_func + async_instance.later_async_func_attr = normal_func + + mock_async_instance = Mock(spec_set=async_instance) + + async_instance.later_async_func_attr = async_func + + self.assertIsInstance(mock_async_instance.async_func_attr, AsyncMock) + # only the shape of the spec at the time of mock construction matters + self.assertNotIsInstance(mock_async_instance.later_async_func_attr, AsyncMock) + def test_spec_mock_type_kw(self): def inner_test(mock_type): async_mock = mock_type(spec=async_func) From 78fed8adfb2a77c651ec0349f7c3af240e70fc97 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Sat, 24 Dec 2022 13:39:39 -0600 Subject: [PATCH 303/388] gh-100287: Fix unittest.mock.seal with AsyncMock (#100496) Backports: e4b43ebb3afbd231a4e5630e7e358aa3093f8677 Signed-off-by: Chris Withers --- .../2022-12-24-08-42-05.gh-issue-100287.n0oEuG.rst | 1 + mock/mock.py | 8 ++++---- mock/tests/testasync.py | 14 +++++++++++++- 3 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 NEWS.d/2022-12-24-08-42-05.gh-issue-100287.n0oEuG.rst diff --git a/NEWS.d/2022-12-24-08-42-05.gh-issue-100287.n0oEuG.rst b/NEWS.d/2022-12-24-08-42-05.gh-issue-100287.n0oEuG.rst new file mode 100644 index 00000000..b353f081 --- /dev/null +++ b/NEWS.d/2022-12-24-08-42-05.gh-issue-100287.n0oEuG.rst @@ -0,0 +1 @@ +Fix the interaction of :func:`unittest.mock.seal` with :class:`unittest.mock.AsyncMock`. diff --git a/mock/mock.py b/mock/mock.py index 4a16ec0e..db067914 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1027,15 +1027,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) - if self._mock_sealed: attribute = f".{kw['name']}" if "name" in kw else "()" mock_name = self._extract_mock_name() + attribute raise AttributeError(mock_name) + _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: # Any asynchronous magic becomes an AsyncMock diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index 8269e5fb..41c22aca 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -7,7 +7,7 @@ from contextlib import contextmanager from mock import (ANY, call, AsyncMock, patch, MagicMock, Mock, - create_autospec, sentinel) + create_autospec, sentinel, seal) from mock.backports import IsolatedAsyncioTestCase, iscoroutinefunction from mock.mock import _CallList @@ -306,6 +306,14 @@ def test_spec_normal_methods_on_class_with_mock(self): self.assertIsInstance(mock.async_method, AsyncMock) self.assertIsInstance(mock.normal_method, Mock) + def test_spec_normal_methods_on_class_with_mock_seal(self): + mock = Mock(AsyncClass) + seal(mock) + with self.assertRaises(AttributeError): + mock.normal_method + with self.assertRaises(AttributeError): + mock.async_method + def test_spec_async_attributes_instance(self): async_instance = AsyncClass() async_instance.async_func_attr = async_func @@ -1097,3 +1105,7 @@ async def f(x=None): pass )) as cm: self.mock.assert_has_awaits([call(), call(1, 2)]) self.assertIsInstance(cm.exception.__cause__, TypeError) + + +if __name__ == '__main__': + unittest.main() From 5462a558e748fd1c6707c8c19d539527aa7e2145 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 28 Dec 2022 12:36:26 +0000 Subject: [PATCH 304/388] Fix mock code coverage. (#100580) Backports: 457c1f4a19a096a52d6553687c7c4cee415818dc Signed-off-by: Chris Withers --- mock/tests/testsealable.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/mock/tests/testsealable.py b/mock/tests/testsealable.py index e7eaec88..5e09b28a 100644 --- a/mock/tests/testsealable.py +++ b/mock/tests/testsealable.py @@ -175,15 +175,12 @@ def test_seal_with_autospec(self): # https://bugs.python.org/issue45156 class Foo: foo = 0 - def bar1(self): - return 1 - def bar2(self): - return 2 + def bar1(self): pass + def bar2(self): pass class Baz: baz = 3 - def ban(self): - return 4 + def ban(self): pass for spec_set in (True, False): with self.subTest(spec_set=spec_set): From 726e5ee6af782d297dfcdc20ee6a0aef4d4e5683 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 28 Dec 2022 12:37:23 +0000 Subject: [PATCH 305/388] latest sync point --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index a2f4c0e5..107fd34e 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -569ca27293eec89b5b41c3a12e6531d3eddf0e1b +457c1f4a19a096a52d6553687c7c4cee415818dc From 85a196ecc05a6f0edc08c74fa16ec62573c24caf Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 28 Dec 2022 12:44:22 +0000 Subject: [PATCH 306/388] Fixup reverting backported rev example. --- docs/index.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/index.txt b/docs/index.txt index a3a7eb78..06d14b6c 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -135,9 +135,14 @@ Backporting rules then commit the latest sync point and then revert the problematic commit in an immediately subsequent commit and make a not of the reason for the revert in that commit message. - See ``1140c2930`` for an example where ``af2a3d6def15`` broke compatibility for all Python + See `bc04ea7`__ for an example where `f4c8dc7`__ broke compatibility for all Python versions earlier than 3.10. + __ https://github.com/testing-cabal/mock/commit/bc04ea76352c2064d79160f13649f879667a89cb + + __ https://github.com/testing-cabal/mock/commit/f4c8dc7098abb6b2f9a65ee86bad3891776abb50 + + Backporting process ------------------- From 3a70a8c54a93b34a2580ee1ab68777a6e0e95f66 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 28 Dec 2022 12:50:56 +0000 Subject: [PATCH 307/388] Update historical issue references to be bpo- prefixed --- CHANGELOG.rst | 174 +++++++++++++++++++++++++------------------------- 1 file changed, 87 insertions(+), 87 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f69bbefe..96285562 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,25 +1,25 @@ 4.0.3 ----- -- Issue #42532: Remove unexpected call of ``__bool__`` when passing a +- bpo-42532: Remove unexpected call of ``__bool__`` when passing a ``spec_arg`` argument to a Mock. -- Issue #39966: Revert bpo-25597. :class:`unittest.mock.MagicMock` with +- bpo-39966: Revert bpo-25597. :class:`unittest.mock.MagicMock` with wraps' set uses default return values for magic methods. -- Issue #41877: Mock objects which are not unsafe will now raise an +- bpo-41877: Mock objects which are not unsafe will now raise an AttributeError if an attribute with the prefix asert, aseert, or assrt is accessed, in addition to this already happening for the prefixes assert or assret. -- Issue #40126: Fixed reverting multiple patches in unittest.mock. Patcher's +- bpo-40126: Fixed reverting multiple patches in unittest.mock. Patcher's ``__exit__()`` is now never called if its ``__enter__()`` is failed. Returning true from ``__exit__()`` silences now the exception. 4.0.2 ----- -- Issue #39915: Ensure :attr:`unittest.mock.AsyncMock.await_args_list` has +- bpo-39915: Ensure :attr:`unittest.mock.AsyncMock.await_args_list` has call objects in the order of awaited arguments instead of using :attr:`unittest.mock.Mock.call_args` which has the last value of the call. Patch by Karthikeyan Singaravelan. @@ -43,7 +43,7 @@ __ https://github.com/python/cpython/commit/4a686504eb2bbf69adf78077458508a7ba131667 -- Issue #37972: Subscripts to the `unittest.mock.call` objects now receive +- bpo-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`: @@ -51,98 +51,98 @@ Patch by blhsing -- Issue #38839: Fix some unused functions in tests. Patch by Adam Johnson. +- bpo-38839: Fix some unused functions in tests. Patch by Adam Johnson. -- Issue #39485: Fix a bug in :func:`unittest.mock.create_autospec` that +- bpo-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 +- bpo-39082: Allow AsyncMock to correctly patch static/class methods -- Issue #38093: Fixes AsyncMock so it doesn't crash when used with +- bpo-38093: Fixes AsyncMock so it doesn't crash when used with AsyncContextManagers or AsyncIterators. -- Issue #38859: AsyncMock now returns StopAsyncIteration on the exaustion of +- bpo-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 +- bpo-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 +- bpo-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 +- bpo-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 +- bpo-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 +- bpo-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 +- bpo-38669: Raise :exc:`TypeError` when passing target as a string with :meth:`unittest.mock.patch.object`. -- Issue #25597: Ensure, if ``wraps`` is supplied to +- bpo-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 +- bpo-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 +- bpo-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. +- bpo-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 +- bpo-38932: Mock fully resets child objects on reset_mock(). Patch by Vegard Stikbakke -- Issue #37685: Fixed ``__eq__``, ``__lt__`` etc implementations in some +- bpo-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 +- bpo-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 +- bpo-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 +- bpo-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 +- bpo-21600: Fix :func:`mock.patch.stopall` to stop active patches that were created with :func:`mock.patch.dict`. -- Issue #38161: Removes _AwaitEvent from AsyncMock. +- bpo-38161: Removes _AwaitEvent from AsyncMock. -- Issue #36871: Ensure method signature is used instead of constructor +- bpo-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 ----- -- Issue #31855: :func:`unittest.mock.mock_open` results now respects the +- bpo-31855: :func:`unittest.mock.mock_open` results now respects the argument of read([size]). Patch contributed by Rémi Lapeyre. 3.0.4 @@ -169,179 +169,179 @@ 3.0.0 ----- -- Issue #35226: Recursively check arguments when testing for equality of +- bpo-35226: Recursively check arguments when testing for equality of :class:`unittest.mock.call` objects and add note that tracking of parameters used to create ancestors of mocks in ``mock_calls`` is not possible. -- Issue #31177: Fix bug that prevented using :meth:`reset_mock +- bpo-31177: Fix bug that prevented using :meth:`reset_mock ` on mock instances with deleted attributes -- Issue #26704: Added test demonstrating double-patching of an instance +- bpo-26704: Added test demonstrating double-patching of an instance method. Patch by Anthony Sottile. -- Issue #35500: Write expected and actual call parameters on separate lines +- bpo-35500: Write expected and actual call parameters on separate lines in :meth:`unittest.mock.Mock.assert_called_with` assertion errors. Contributed by Susan Su. -- Issue #35330: When a :class:`Mock` instance was used to wrap an object, if +- bpo-35330: When a :class:`Mock` instance was used to wrap an object, if `side_effect` is used in one of the mocks of it methods, don't call the original implementation and return the result of using the side effect the same way that it is done with return_value. -- Issue #30541: Add new function to seal a mock and prevent the +- bpo-30541: Add new function to seal a mock and prevent the automatically creation of child mocks. Patch by Mario Corchero. -- Issue #35022: :class:`unittest.mock.MagicMock` now supports the +- bpo-35022: :class:`unittest.mock.MagicMock` now supports the ``__fspath__`` method (from :class:`os.PathLike`). -- Issue #33516: :class:`unittest.mock.MagicMock` now supports the +- bpo-33516: :class:`unittest.mock.MagicMock` now supports the ``__round__`` magic method. -- Issue #35512: :func:`unittest.mock.patch.dict` used as a decorator with +- bpo-35512: :func:`unittest.mock.patch.dict` used as a decorator with string target resolves the target during function call instead of during decorator construction. Patch by Karthikeyan Singaravelan. -- Issue #36366: Calling ``stop()`` on an unstarted or stopped +- bpo-36366: Calling ``stop()`` on an unstarted or stopped :func:`unittest.mock.patch` object will now return `None` instead of raising :exc:`RuntimeError`, making the method idempotent. Patch byKarthikeyan Singaravelan. -- Issue #35357: Internal attributes' names of unittest.mock._Call and +- bpo-35357: Internal attributes' names of unittest.mock._Call and unittest.mock.MagicProxy (name, parent & from_kall) are now prefixed with _mock_ in order to prevent clashes with widely used object attributes. Fixed minor typo in test function name. -- Issue #20239: Allow repeated assignment deletion of +- bpo-20239: Allow repeated assignment deletion of :class:`unittest.mock.Mock` attributes. Patch by Pablo Galindo. -- Issue #35082: Don't return deleted attributes when calling dir on a +- bpo-35082: Don't return deleted attributes when calling dir on a :class:`unittest.mock.Mock`. -- Issue #0: Improved an error message when mock assert_has_calls fails. +- bpo-0: Improved an error message when mock assert_has_calls fails. -- Issue #23078: Add support for :func:`classmethod` and :func:`staticmethod` +- bpo-23078: Add support for :func:`classmethod` and :func:`staticmethod` to :func:`unittest.mock.create_autospec`. Initial patch by Felipe Ochoa. -- Issue #21478: Calls to a child function created with +- bpo-21478: Calls to a child function created with :func:`unittest.mock.create_autospec` should propagate to the parent. Patch by Karthikeyan Singaravelan. -- Issue #36598: Fix ``isinstance`` check for Mock objects with spec when the +- bpo-36598: Fix ``isinstance`` check for Mock objects with spec when the code is executed under tracing. Patch by Karthikeyan Singaravelan. -- Issue #32933: :func:`unittest.mock.mock_open` now supports iteration over +- bpo-32933: :func:`unittest.mock.mock_open` now supports iteration over the file contents. Patch by Tony Flury. -- Issue #21269: Add ``args`` and ``kwargs`` properties to mock call objects. +- bpo-21269: Add ``args`` and ``kwargs`` properties to mock call objects. Contributed by Kumar Akshay. -- Issue #17185: Set ``__signature__`` on mock for :mod:`inspect` to get +- bpo-17185: Set ``__signature__`` on mock for :mod:`inspect` to get signature. Patch by Karthikeyan Singaravelan. -- Issue #35047: ``unittest.mock`` now includes mock calls in exception +- bpo-35047: ``unittest.mock`` now includes mock calls in exception messages if ``assert_not_called``, ``assert_called_once``, or ``assert_called_once_with`` fails. Patch by Petter Strandmark. -- Issue #28380: unittest.mock Mock autospec functions now properly support +- bpo-28380: unittest.mock Mock autospec functions now properly support assert_called, assert_not_called, and assert_called_once. -- Issue #28735: Fixed the comparison of mock.MagickMock with mock.ANY. +- bpo-28735: Fixed the comparison of mock.MagickMock with mock.ANY. -- Issue #20804: The unittest.mock.sentinel attributes now preserve their +- bpo-20804: The unittest.mock.sentinel attributes now preserve their identity when they are copied or pickled. -- Issue #28961: Fix unittest.mock._Call helper: don't ignore the name parameter +- bpo-28961: Fix unittest.mock._Call helper: don't ignore the name parameter anymore. Patch written by Jiajun Huang. -- Issue #26750: unittest.mock.create_autospec() now works properly for +- bpo-26750: unittest.mock.create_autospec() now works properly for subclasses of property() and other data descriptors. -- Issue #21271: New keyword only parameters in reset_mock call. +- bpo-21271: New keyword only parameters in reset_mock call. -- Issue #26807: mock_open 'files' no longer error on readline at end of file. +- bpo-26807: mock_open 'files' no longer error on readline at end of file. Patch from Yolanda Robla. -- Issue #25195: Fix a regression in mock.MagicMock. _Call is a subclass of +- bpo-25195: Fix a regression in mock.MagicMock. _Call is a subclass of tuple (changeset 3603bae63c13 only works for classes) so we need to implement __ne__ ourselves. Patch by Andrew Plummer. 2.0.0 and earlier ----------------- -- Issue #26323: Add Mock.assert_called() and Mock.assert_called_once() +- bpo-26323: Add Mock.assert_called() and Mock.assert_called_once() methods to unittest.mock. Patch written by Amit Saha. -- Issue #22138: Fix mock.patch behavior when patching descriptors. Restore +- bpo-22138: Fix mock.patch behavior when patching descriptors. Restore original values after patching. Patch contributed by Sean McCully. -- Issue #24857: Comparing call_args to a long sequence now correctly returns a +- bpo-24857: Comparing call_args to a long sequence now correctly returns a boolean result instead of raising an exception. Patch by A Kaptur. -- Issue #23004: mock_open() now reads binary data correctly when the type of +- bpo-23004: mock_open() now reads binary data correctly when the type of read_data is bytes. Initial patch by Aaron Hill. -- Issue #21750: mock_open.read_data can now be read from each instance, as it +- bpo-21750: mock_open.read_data can now be read from each instance, as it could in Python 3.3. -- Issue #18622: unittest.mock.mock_open().reset_mock would recurse infinitely. +- bpo-18622: unittest.mock.mock_open().reset_mock would recurse infinitely. Patch from Nicola Palumbo and Laurent De Buyst. -- Issue #23661: unittest.mock side_effects can now be exceptions again. This +- bpo-23661: unittest.mock side_effects can now be exceptions again. This was a regression vs Python 3.4. Patch from Ignacio Rossi -- Issue #23310: Fix MagicMock's initializer to work with __methods__, just +- bpo-23310: Fix MagicMock's initializer to work with __methods__, just like configure_mock(). Patch by Kasia Jachim. -- Issue #23568: Add rdivmod support to MagicMock() objects. +- bpo-23568: Add rdivmod support to MagicMock() objects. Patch by Håkan Lövdahl. -- Issue #23581: Add matmul support to MagicMock. Patch by Håkan Lövdahl. +- bpo-23581: Add matmul support to MagicMock. Patch by Håkan Lövdahl. -- Issue #23326: Removed __ne__ implementations. Since fixing default __ne__ - implementation in issue #21408 they are redundant. *** NOT BACKPORTED *** +- bpo-23326: Removed __ne__ implementations. Since fixing default __ne__ + implementation in bpo-21408 they are redundant. *** NOT BACKPORTED *** -- Issue #21270: We now override tuple methods in mock.call objects so that +- bpo-21270: We now override tuple methods in mock.call objects so that they can be used as normal call attributes. -- Issue #21256: Printout of keyword args should be in deterministic order in +- bpo-21256: Printout of keyword args should be in deterministic order in a mock function call. This will help to write better doctests. -- Issue #21262: New method assert_not_called for Mock. +- bpo-21262: New method assert_not_called for Mock. It raises AssertionError if the mock has been called. -- Issue #21238: New keyword argument `unsafe` to Mock. It raises +- bpo-21238: New keyword argument `unsafe` to Mock. It raises `AttributeError` incase of an attribute startswith assert or assret. -- Issue #21239: patch.stopall() didn't work deterministically when the same +- bpo-21239: patch.stopall() didn't work deterministically when the same name was patched more than once. -- Issue #21222: Passing name keyword argument to mock.create_autospec now +- bpo-21222: Passing name keyword argument to mock.create_autospec now works. -- Issue #17826: setting an iterable side_effect on a mock function created by +- bpo-17826: setting an iterable side_effect on a mock function created by create_autospec now works. Patch by Kushal Das. -- Issue #17826: setting an iterable side_effect on a mock function created by +- bpo-17826: setting an iterable side_effect on a mock function created by create_autospec now works. Patch by Kushal Das. -- Issue #20968: unittest.mock.MagicMock now supports division. +- bpo-20968: unittest.mock.MagicMock now supports division. Patch by Johannes Baiter. -- Issue #20189: unittest.mock now no longer assumes that any object for +- bpo-20189: unittest.mock now no longer assumes that any object for which it could get an inspect.Signature is a callable written in Python. Fix courtesy of Michael Foord. -- Issue #17467: add readline and readlines support to mock_open in +- bpo-17467: add readline and readlines support to mock_open in unittest.mock. -- Issue #17015: When it has a spec, a Mock object now inspects its signature +- bpo-17015: When it has a spec, a Mock object now inspects its signature when matching calls, so that arguments can be matched positionally or by name. -- Issue #15323: improve failure message of Mock.assert_called_once_with +- bpo-15323: improve failure message of Mock.assert_called_once_with -- Issue #14857: fix regression in references to PEP 3135 implicit __class__ - closure variable (Reopens issue #12370) +- bpo-14857: fix regression in references to PEP 3135 implicit __class__ + closure variable (Reopens bpo-12370) -- Issue #14295: Add unittest.mock +- bpo-14295: Add unittest.mock From c4dd650897a2902e2b547329393675dc0227821b Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 28 Dec 2022 12:52:03 +0000 Subject: [PATCH 308/388] Preparing for 5.0.0 release. --- CHANGELOG.rst | 61 +++++++++++++++++++ .../2020-12-10-09-24-44.bpo-41877.iJSCvM.rst | 1 - .../2020-12-10-19-49-52.bpo-41877.wiVlPc.rst | 1 - .../2021-04-10-03-30-36.bpo-43478.iZcBTq.rst | 1 - .../2021-08-26-09-54-14.bpo-45010.Cn23bQ.rst | 2 - .../2021-09-13-00-28-17.bpo-45156.8oomV3.rst | 2 - .../2022-01-23-18-04-45.bpo-41403.SgoHqV.rst | 3 - .../2022-02-03-00-21-32.bpo-43478.0nfcam.rst | 1 - .../2022-02-25-01-42-45.bpo-46852.nkRDvV.rst | 4 -- ...2-05-03-11-32-29.gh-issue-91803.pI4Juv.rst | 3 - ...2-06-21-11-40-31.gh-issue-84753.FW1pxO.rst | 3 - ...2-10-08-19-39-27.gh-issue-98086.y---WC.rst | 1 - ...2-10-25-20-17-34.gh-issue-98624.YQUPFy.rst | 2 - ...2-12-14-17-37-01.gh-issue-83076.NaYzWT.rst | 1 - ...-12-24-08-42-05.gh-issue-100287.n0oEuG.rst | 1 - mock/__init__.py | 2 +- 16 files changed, 62 insertions(+), 27 deletions(-) delete mode 100644 NEWS.d/2020-12-10-09-24-44.bpo-41877.iJSCvM.rst delete mode 100644 NEWS.d/2020-12-10-19-49-52.bpo-41877.wiVlPc.rst delete mode 100644 NEWS.d/2021-04-10-03-30-36.bpo-43478.iZcBTq.rst delete mode 100644 NEWS.d/2021-08-26-09-54-14.bpo-45010.Cn23bQ.rst delete mode 100644 NEWS.d/2021-09-13-00-28-17.bpo-45156.8oomV3.rst delete mode 100644 NEWS.d/2022-01-23-18-04-45.bpo-41403.SgoHqV.rst delete mode 100644 NEWS.d/2022-02-03-00-21-32.bpo-43478.0nfcam.rst delete mode 100644 NEWS.d/2022-02-25-01-42-45.bpo-46852.nkRDvV.rst delete mode 100644 NEWS.d/2022-05-03-11-32-29.gh-issue-91803.pI4Juv.rst delete mode 100644 NEWS.d/2022-06-21-11-40-31.gh-issue-84753.FW1pxO.rst delete mode 100644 NEWS.d/2022-10-08-19-39-27.gh-issue-98086.y---WC.rst delete mode 100644 NEWS.d/2022-10-25-20-17-34.gh-issue-98624.YQUPFy.rst delete mode 100644 NEWS.d/2022-12-14-17-37-01.gh-issue-83076.NaYzWT.rst delete mode 100644 NEWS.d/2022-12-24-08-42-05.gh-issue-100287.n0oEuG.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 96285562..386fb4cb 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,64 @@ +5.0.0 +----- + +- gh-98624: Add a mutex to unittest.mock.NonCallableMock to protect + concurrent access to mock attributes. + +- bpo-43478: Mocks can no longer be used as the specs for other Mocks. As a + result, an already-mocked object cannot have an attribute mocked using + `autospec=True` or be the subject of a `create_autospec(...)` call. This + can uncover bugs in tests since these Mock-derived Mocks will always pass + certain tests (e.g. isinstance) and builtin assert functions (e.g. + assert_called_once_with) will unconditionally pass. + +- bpo-45156: Fixes infinite loop on :func:`unittest.mock.seal` of mocks + created by :func:`~unittest.create_autospec`. + +- bpo-41403: Make :meth:`mock.patch` raise a :exc:`TypeError` with a + relevant error message on invalid arg. Previously it allowed a cryptic + :exc:`AttributeError` to escape. + +- gh-91803: Fix an error when using a method of objects mocked with + :func:`unittest.mock.create_autospec` after it was sealed with + :func:`unittest.mock.seal` function. + +- bpo-41877: AttributeError for suspected misspellings of assertions on + mocks are now pointing out that the cause are misspelled assertions and + also what to do if the misspelling is actually an intended attribute name. + The unittest.mock document is also updated to reflect the current set of + recognised misspellings. + +- bpo-43478: Mocks can no longer be provided as the specs for other Mocks. + As a result, an already-mocked object cannot be passed to `mock.Mock()`. + This can uncover bugs in tests since these Mock-derived Mocks will always + pass certain tests (e.g. isinstance) and builtin assert functions (e.g. + assert_called_once_with) will unconditionally pass. + +- bpo-45010: Remove support of special method ``__div__`` in + :mod:`unittest.mock`. It is not used in Python 3. + +- gh-84753: :func:`inspect.iscoroutinefunction` now properly returns + ``True`` when an instance of :class:`unittest.mock.AsyncMock` is passed to + it. This makes it consistent with behavior of + :func:`asyncio.iscoroutinefunction`. Patch by Mehdi ABAAKOUK. + +- bpo-46852: Remove the undocumented private ``float.__set_format__()`` + method, previously known as ``float.__setformat__()`` in Python 3.7. Its + docstring said: "You probably don't want to use this function. It exists + mainly to be used in Python's test suite." Patch by Victor Stinner. + +- gh-98086: Make sure ``patch.dict()`` can be applied on async functions. + +- gh-100287: Fix the interaction of :func:`unittest.mock.seal` with + :class:`unittest.mock.AsyncMock`. + +- gh-83076: Instantiation of ``Mock()`` and ``AsyncMock()`` is now 3.8x + faster. + +- bpo-41877: A check is added against misspellings of autospect, auto_spec + and set_spec being passed as arguments to patch, patch.object and + create_autospec. + 4.0.3 ----- diff --git a/NEWS.d/2020-12-10-09-24-44.bpo-41877.iJSCvM.rst b/NEWS.d/2020-12-10-09-24-44.bpo-41877.iJSCvM.rst deleted file mode 100644 index df43cc5d..00000000 --- a/NEWS.d/2020-12-10-09-24-44.bpo-41877.iJSCvM.rst +++ /dev/null @@ -1 +0,0 @@ -AttributeError for suspected misspellings of assertions on mocks are now pointing out that the cause are misspelled assertions and also what to do if the misspelling is actually an intended attribute name. The unittest.mock document is also updated to reflect the current set of recognised misspellings. \ No newline at end of file diff --git a/NEWS.d/2020-12-10-19-49-52.bpo-41877.wiVlPc.rst b/NEWS.d/2020-12-10-19-49-52.bpo-41877.wiVlPc.rst deleted file mode 100644 index d42200ec..00000000 --- a/NEWS.d/2020-12-10-19-49-52.bpo-41877.wiVlPc.rst +++ /dev/null @@ -1 +0,0 @@ -A check is added against misspellings of autospect, auto_spec and set_spec being passed as arguments to patch, patch.object and create_autospec. \ No newline at end of file diff --git a/NEWS.d/2021-04-10-03-30-36.bpo-43478.iZcBTq.rst b/NEWS.d/2021-04-10-03-30-36.bpo-43478.iZcBTq.rst deleted file mode 100644 index aaa1992f..00000000 --- a/NEWS.d/2021-04-10-03-30-36.bpo-43478.iZcBTq.rst +++ /dev/null @@ -1 +0,0 @@ -Mocks can no longer be used as the specs for other Mocks. As a result, an already-mocked object cannot have an attribute mocked using `autospec=True` or be the subject of a `create_autospec(...)` call. This can uncover bugs in tests since these Mock-derived Mocks will always pass certain tests (e.g. isinstance) and builtin assert functions (e.g. assert_called_once_with) will unconditionally pass. \ No newline at end of file diff --git a/NEWS.d/2021-08-26-09-54-14.bpo-45010.Cn23bQ.rst b/NEWS.d/2021-08-26-09-54-14.bpo-45010.Cn23bQ.rst deleted file mode 100644 index bdf1bfe1..00000000 --- a/NEWS.d/2021-08-26-09-54-14.bpo-45010.Cn23bQ.rst +++ /dev/null @@ -1,2 +0,0 @@ -Remove support of special method ``__div__`` in :mod:`unittest.mock`. It is -not used in Python 3. diff --git a/NEWS.d/2021-09-13-00-28-17.bpo-45156.8oomV3.rst b/NEWS.d/2021-09-13-00-28-17.bpo-45156.8oomV3.rst deleted file mode 100644 index b2094b57..00000000 --- a/NEWS.d/2021-09-13-00-28-17.bpo-45156.8oomV3.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fixes infinite loop on :func:`unittest.mock.seal` of mocks created by -:func:`~unittest.create_autospec`. diff --git a/NEWS.d/2022-01-23-18-04-45.bpo-41403.SgoHqV.rst b/NEWS.d/2022-01-23-18-04-45.bpo-41403.SgoHqV.rst deleted file mode 100644 index ede159b2..00000000 --- a/NEWS.d/2022-01-23-18-04-45.bpo-41403.SgoHqV.rst +++ /dev/null @@ -1,3 +0,0 @@ -Make :meth:`mock.patch` raise a :exc:`TypeError` with a relevant error -message on invalid arg. Previously it allowed a cryptic -:exc:`AttributeError` to escape. diff --git a/NEWS.d/2022-02-03-00-21-32.bpo-43478.0nfcam.rst b/NEWS.d/2022-02-03-00-21-32.bpo-43478.0nfcam.rst deleted file mode 100644 index 7c8fc47c..00000000 --- a/NEWS.d/2022-02-03-00-21-32.bpo-43478.0nfcam.rst +++ /dev/null @@ -1 +0,0 @@ -Mocks can no longer be provided as the specs for other Mocks. As a result, an already-mocked object cannot be passed to `mock.Mock()`. This can uncover bugs in tests since these Mock-derived Mocks will always pass certain tests (e.g. isinstance) and builtin assert functions (e.g. assert_called_once_with) will unconditionally pass. diff --git a/NEWS.d/2022-02-25-01-42-45.bpo-46852.nkRDvV.rst b/NEWS.d/2022-02-25-01-42-45.bpo-46852.nkRDvV.rst deleted file mode 100644 index cd0049a4..00000000 --- a/NEWS.d/2022-02-25-01-42-45.bpo-46852.nkRDvV.rst +++ /dev/null @@ -1,4 +0,0 @@ -Remove the undocumented private ``float.__set_format__()`` method, previously -known as ``float.__setformat__()`` in Python 3.7. Its docstring said: "You -probably don't want to use this function. It exists mainly to be used in -Python's test suite." Patch by Victor Stinner. diff --git a/NEWS.d/2022-05-03-11-32-29.gh-issue-91803.pI4Juv.rst b/NEWS.d/2022-05-03-11-32-29.gh-issue-91803.pI4Juv.rst deleted file mode 100644 index 14829e8f..00000000 --- a/NEWS.d/2022-05-03-11-32-29.gh-issue-91803.pI4Juv.rst +++ /dev/null @@ -1,3 +0,0 @@ -Fix an error when using a method of objects mocked with -:func:`unittest.mock.create_autospec` after it was sealed with -:func:`unittest.mock.seal` function. diff --git a/NEWS.d/2022-06-21-11-40-31.gh-issue-84753.FW1pxO.rst b/NEWS.d/2022-06-21-11-40-31.gh-issue-84753.FW1pxO.rst deleted file mode 100644 index f701d2a1..00000000 --- a/NEWS.d/2022-06-21-11-40-31.gh-issue-84753.FW1pxO.rst +++ /dev/null @@ -1,3 +0,0 @@ -:func:`inspect.iscoroutinefunction` now properly returns ``True`` when an instance -of :class:`unittest.mock.AsyncMock` is passed to it. This makes it consistent with -behavior of :func:`asyncio.iscoroutinefunction`. Patch by Mehdi ABAAKOUK. diff --git a/NEWS.d/2022-10-08-19-39-27.gh-issue-98086.y---WC.rst b/NEWS.d/2022-10-08-19-39-27.gh-issue-98086.y---WC.rst deleted file mode 100644 index f4a1d272..00000000 --- a/NEWS.d/2022-10-08-19-39-27.gh-issue-98086.y---WC.rst +++ /dev/null @@ -1 +0,0 @@ -Make sure ``patch.dict()`` can be applied on async functions. diff --git a/NEWS.d/2022-10-25-20-17-34.gh-issue-98624.YQUPFy.rst b/NEWS.d/2022-10-25-20-17-34.gh-issue-98624.YQUPFy.rst deleted file mode 100644 index fb3a2b83..00000000 --- a/NEWS.d/2022-10-25-20-17-34.gh-issue-98624.YQUPFy.rst +++ /dev/null @@ -1,2 +0,0 @@ -Add a mutex to unittest.mock.NonCallableMock to protect concurrent access -to mock attributes. diff --git a/NEWS.d/2022-12-14-17-37-01.gh-issue-83076.NaYzWT.rst b/NEWS.d/2022-12-14-17-37-01.gh-issue-83076.NaYzWT.rst deleted file mode 100644 index a4984e69..00000000 --- a/NEWS.d/2022-12-14-17-37-01.gh-issue-83076.NaYzWT.rst +++ /dev/null @@ -1 +0,0 @@ -Instantiation of ``Mock()`` and ``AsyncMock()`` is now 3.8x faster. diff --git a/NEWS.d/2022-12-24-08-42-05.gh-issue-100287.n0oEuG.rst b/NEWS.d/2022-12-24-08-42-05.gh-issue-100287.n0oEuG.rst deleted file mode 100644 index b353f081..00000000 --- a/NEWS.d/2022-12-24-08-42-05.gh-issue-100287.n0oEuG.rst +++ /dev/null @@ -1 +0,0 @@ -Fix the interaction of :func:`unittest.mock.seal` with :class:`unittest.mock.AsyncMock`. diff --git a/mock/__init__.py b/mock/__init__.py index dbe8031b..0e72ce6d 100644 --- a/mock/__init__.py +++ b/mock/__init__.py @@ -7,7 +7,7 @@ import mock.mock as _mock from mock.mock import * -__version__ = '4.0.3' +__version__ = '5.0.0' version_info = tuple(int(p) for p in re.match(r'(\d+).(\d+).(\d+)', __version__).groups()) From 172a36cea5b2571fd0ba1d59799ba41a42930880 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 28 Dec 2022 12:52:59 +0000 Subject: [PATCH 309/388] Add support for gh issues to release.py. --- release.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/release.py b/release.py index 3fa9406f..a44dece0 100644 --- a/release.py +++ b/release.py @@ -27,8 +27,10 @@ def text_from_news(): text = [] for metadata, body in blurbs: - bpo = metadata['bpo'] - body = f"- Issue #{bpo}: " + body + bpo = metadata.get('bpo') + gh = metadata.get('gh-issue') + issue = f'bpo-{bpo}' if bpo else f'gh-{gh}' + body = f"- {issue}: " + body text.append(blurb_module.textwrap_body(body, subsequent_indent=' ')) return '\n'.join(text) From 6d4014827a04bbb8b63128b060ec70d321f38d95 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 28 Dec 2022 12:58:21 +0000 Subject: [PATCH 310/388] sudo no longer needed --- .carthorse.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.carthorse.yml b/.carthorse.yml index 7b6ca85b..1bee3707 100644 --- a/.carthorse.yml +++ b/.carthorse.yml @@ -4,6 +4,6 @@ carthorse: when: - version-not-tagged actions: - - run: "sudo pip install -e .[build]" + - run: "pip install -e .[build]" - run: "twine upload -u __token__ -p $PYPI_TOKEN dist/*" - create-tag From 135ca36adb30e9d87dde9098d3e043b2251cfb75 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 3 Jan 2023 10:18:13 +0000 Subject: [PATCH 311/388] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index bc950b71..593dda5e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -12,7 +12,7 @@ As such, any problems you encounter most likely need to be fixed upstream. Before submitting an issue here, please try and reproduce the problem on the latest release of Python 3, including alphas, and replace any import from `mock` with ones from `unittest.mock`. -If the issue still occurs, then please report upstream through https://bugs.python.org/ as it will need to be fixed there so that it can be backported here and released to you. +If the issue still occurs, then please report upstream through https://github.com/python/cpython/issues as it will need to be fixed there so that it can be backported here and released to you. If the issue does not occur upstream, please file an issue using the template below as it may be an issue specific to the backport: From aeca482230257d3a2a5eb5da9d1bd65359bcb0d6 Mon Sep 17 00:00:00 2001 From: Christian Klein <167265+cklein@users.noreply.github.com> Date: Wed, 4 Jan 2023 23:31:29 +0100 Subject: [PATCH 312/388] gh-100739: Respect mock spec when checking for unsafe prefixes (#100740) Co-authored-by: Nikita Sobolev Backports: 7f1eefc6f4843f0fca60308f557a71af11d18a53 Signed-off-by: Chris Withers --- ...023-01-04-09-53-38.gh-issue-100740.-j5UjI.rst | 1 + mock/mock.py | 2 +- mock/tests/testmock.py | 16 ++++++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 NEWS.d/2023-01-04-09-53-38.gh-issue-100740.-j5UjI.rst diff --git a/NEWS.d/2023-01-04-09-53-38.gh-issue-100740.-j5UjI.rst b/NEWS.d/2023-01-04-09-53-38.gh-issue-100740.-j5UjI.rst new file mode 100644 index 00000000..4753e7b4 --- /dev/null +++ b/NEWS.d/2023-01-04-09-53-38.gh-issue-100740.-j5UjI.rst @@ -0,0 +1 @@ +Fix ``unittest.mock.Mock`` not respecting the spec for attribute names prefixed with ``assert``. diff --git a/mock/mock.py b/mock/mock.py index db067914..040dd770 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -655,7 +655,7 @@ def __getattr__(self, name): raise AttributeError("Mock object has no attribute %r" % name) elif _is_magic(name): raise AttributeError(name) - if not self._mock_unsafe: + if not self._mock_unsafe and (not self._mock_methods or name not in self._mock_methods): if name.startswith(('assert', 'assret', 'asert', 'aseert', 'assrt')): raise AttributeError( f"{name!r} is not a valid assertion. Use a spec " diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index e00baf9a..3770e202 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -1655,6 +1655,22 @@ def test_mock_unsafe(self): m.aseert_foo_call() m.assrt_foo_call() + # gh-100739 + def test_mock_safe_with_spec(self): + class Foo(object): + def assert_bar(self): + pass + + def assertSome(self): + pass + + m = Mock(spec=Foo) + m.assert_bar() + m.assertSome() + + m.assert_bar.assert_called_once() + m.assertSome.assert_called_once() + #Issue21262 def test_assert_not_called(self): m = Mock() From 21787a948cf1186f3bd38462d5b912784fa81218 Mon Sep 17 00:00:00 2001 From: Christian Klein <167265+cklein@users.noreply.github.com> Date: Fri, 6 Jan 2023 19:38:50 +0100 Subject: [PATCH 313/388] gh-100690: Raise an AttributeError when the assert_ prefix is forgotten when using Mock (#100691) Mock objects which are not unsafe will now raise an AttributeError when accessing an attribute that matches the name of an assertion but without the prefix `assert_`, e.g. accessing `called_once` instead of `assert_called_once`. This is in addition to this already happening for accessing attributes with prefixes assert, assret, asert, aseert, and assrt. Backports: 1d4d677d1c90fcf4886ded0bf04b8f9d5b60b909 Signed-off-by: Chris Withers --- ...-01-02-16-59-49.gh-issue-100690.2EgWPS.rst | 7 ++++++ mock/mock.py | 14 +++++++---- mock/tests/testmock.py | 24 +++++++++++++++++++ 3 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 NEWS.d/2023-01-02-16-59-49.gh-issue-100690.2EgWPS.rst diff --git a/NEWS.d/2023-01-02-16-59-49.gh-issue-100690.2EgWPS.rst b/NEWS.d/2023-01-02-16-59-49.gh-issue-100690.2EgWPS.rst new file mode 100644 index 00000000..3796772a --- /dev/null +++ b/NEWS.d/2023-01-02-16-59-49.gh-issue-100690.2EgWPS.rst @@ -0,0 +1,7 @@ +``Mock`` objects which are not unsafe will now raise an +``AttributeError`` when accessing an attribute that matches the name +of an assertion but without the prefix ``assert_``, e.g. accessing +``called_once`` instead of ``assert_called_once``. +This is in addition to this already happening for accessing attributes +with prefixes ``assert``, ``assret``, ``asert``, ``aseert``, +and ``assrt``. diff --git a/mock/mock.py b/mock/mock.py index 040dd770..0b4551bb 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -656,7 +656,7 @@ def __getattr__(self, name): elif _is_magic(name): raise AttributeError(name) if not self._mock_unsafe and (not self._mock_methods or name not in self._mock_methods): - if name.startswith(('assert', 'assret', 'asert', 'aseert', 'assrt')): + if name.startswith(('assert', 'assret', 'asert', 'aseert', 'assrt')) or name in ATTRIB_DENY_LIST: raise AttributeError( f"{name!r} is not a valid assertion. Use a spec " f"for the mock if {name!r} is meant to be an attribute.") @@ -1070,6 +1070,10 @@ def _calls_repr(self, prefix="Calls"): return f"\n{prefix}: {safe_repr(self.mock_calls)}." +# Denylist for forbidden attribute names in safe mode +ATTRIB_DENY_LIST = {name.removeprefix("assert_") for name in dir(NonCallableMock) if name.startswith("assert_")} + + 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 @@ -1241,9 +1245,11 @@ class or instance) that acts as the specification for the mock object. If `return_value` attribute. * `unsafe`: By default, accessing any attribute whose name starts with - *assert*, *assret*, *asert*, *aseert* or *assrt* will raise an - AttributeError. Passing `unsafe=True` will allow access to - these attributes. + *assert*, *assret*, *asert*, *aseert*, or *assrt* raises an AttributeError. + Additionally, an AttributeError is raised when accessing + attributes that match the name of an assertion method without the prefix + `assert_`, e.g. accessing `called_once` instead of `assert_called_once`. + Passing `unsafe=True` will allow access to these attributes. * `wraps`: Item for the mock object to wrap. If `wraps` is not None then calling the Mock will pass the call through to the wrapped object diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 3770e202..41b0ab8f 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -1648,12 +1648,36 @@ def test_mock_unsafe(self): m.aseert_foo_call() with self.assertRaisesRegex(AttributeError, msg): m.assrt_foo_call() + with self.assertRaisesRegex(AttributeError, msg): + m.called_once_with() + with self.assertRaisesRegex(AttributeError, msg): + m.called_once() + with self.assertRaisesRegex(AttributeError, msg): + m.has_calls() + + class Foo(object): + def called_once(self): + pass + + def has_calls(self): + pass + + m = Mock(spec=Foo) + m.called_once() + m.has_calls() + + m.called_once.assert_called_once() + m.has_calls.assert_called_once() + m = Mock(unsafe=True) m.assert_foo_call() m.assret_foo_call() m.asert_foo_call() m.aseert_foo_call() m.assrt_foo_call() + m.called_once() + m.called_once_with() + m.has_calls() # gh-100739 def test_mock_safe_with_spec(self): From 18046f149b60dee1a486778da347d0100083da7d Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Sat, 7 Jan 2023 13:25:05 +0300 Subject: [PATCH 314/388] gh-100690: [mock] hide `ATTRIB_DENY_LIST` and make it immutable (#100819) Backports: a109454e828ce2d9bde15dea78405f8ffee653ec Signed-off-by: Chris Withers --- mock/mock.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mock/mock.py b/mock/mock.py index 0b4551bb..f6471fa5 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -656,7 +656,7 @@ def __getattr__(self, name): elif _is_magic(name): raise AttributeError(name) if not self._mock_unsafe and (not self._mock_methods or name not in self._mock_methods): - if name.startswith(('assert', 'assret', 'asert', 'aseert', 'assrt')) or name in ATTRIB_DENY_LIST: + if name.startswith(('assert', 'assret', 'asert', 'aseert', 'assrt')) or name in _ATTRIB_DENY_LIST: raise AttributeError( f"{name!r} is not a valid assertion. Use a spec " f"for the mock if {name!r} is meant to be an attribute.") @@ -1071,7 +1071,11 @@ def _calls_repr(self, prefix="Calls"): # Denylist for forbidden attribute names in safe mode -ATTRIB_DENY_LIST = {name.removeprefix("assert_") for name in dir(NonCallableMock) if name.startswith("assert_")} +_ATTRIB_DENY_LIST = frozenset({ + name.removeprefix("assert_") + for name in dir(NonCallableMock) + if name.startswith("assert_") +}) class _AnyComparer(list): From 521960196a665d6e53807277407cac9c9f933bdf Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 9 Jan 2023 09:23:37 +0000 Subject: [PATCH 315/388] Backwards compatibility fix for 3.8 and earlier. --- mock/mock.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/mock/mock.py b/mock/mock.py index f6471fa5..6d764534 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1070,9 +1070,16 @@ def _calls_repr(self, prefix="Calls"): return f"\n{prefix}: {safe_repr(self.mock_calls)}." +try: + removeprefix = str.removeprefix +except AttributeError: + # Py 3.8 and earlier: + def removeprefix(name, prefix): + return name[len(prefix):] + # Denylist for forbidden attribute names in safe mode _ATTRIB_DENY_LIST = frozenset({ - name.removeprefix("assert_") + removeprefix(name, "assert_") for name in dir(NonCallableMock) if name.startswith("assert_") }) From ee9744d2d17e67e9f28d485e51df84d3154bc8b8 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Sat, 7 Jan 2023 13:49:15 +0300 Subject: [PATCH 316/388] gh-96127: Fix `inspect.signature` call on mocks (#96335) Backports: 9e7d7266ecdcccc02385fe4ccb094f3444102e26 Signed-off-by: Chris Withers --- NEWS.d/2022-08-27-10-35-50.gh-issue-96127.8RdLre.rst | 2 ++ mock/mock.py | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 NEWS.d/2022-08-27-10-35-50.gh-issue-96127.8RdLre.rst diff --git a/NEWS.d/2022-08-27-10-35-50.gh-issue-96127.8RdLre.rst b/NEWS.d/2022-08-27-10-35-50.gh-issue-96127.8RdLre.rst new file mode 100644 index 00000000..79edd8fd --- /dev/null +++ b/NEWS.d/2022-08-27-10-35-50.gh-issue-96127.8RdLre.rst @@ -0,0 +1,2 @@ +``inspect.signature`` was raising ``TypeError`` on call with mock objects. +Now it correctly returns ``(*args, **kwargs)`` as infered signature. diff --git a/mock/mock.py b/mock/mock.py index 6d764534..bf292b42 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2258,7 +2258,15 @@ def __init__(self, *args, **kwargs): code_mock = NonCallableMock(spec_set=_CODE_ATTRS) code_mock.__dict__["_spec_class"] = CodeType code_mock.__dict__["_spec_signature"] = _CODE_SIG - code_mock.co_flags = inspect.CO_COROUTINE + code_mock.co_flags = ( + inspect.CO_COROUTINE + + inspect.CO_VARARGS + + inspect.CO_VARKEYWORDS + ) + code_mock.co_argcount = 0 + code_mock.co_varnames = ('args', 'kwargs') + code_mock.co_posonlyargcount = 0 + code_mock.co_kwonlyargcount = 0 self.__dict__['__code__'] = code_mock self.__dict__['__name__'] = 'AsyncMock' self.__dict__['__defaults__'] = tuple() From 7df4d384393d9d8bf19ec6b89d0a35390b6f5435 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 9 Jan 2023 09:28:42 +0000 Subject: [PATCH 317/388] fix up mock tests coverage Backports: d3642c9a6a65d7edee2ea210d02a75a364df186e Signed-off-by: Chris Withers --- mock/tests/testmock.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 41b0ab8f..f6db14ae 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -1656,11 +1656,9 @@ def test_mock_unsafe(self): m.has_calls() class Foo(object): - def called_once(self): - pass + def called_once(self): pass - def has_calls(self): - pass + def has_calls(self): pass m = Mock(spec=Foo) m.called_once() @@ -1682,11 +1680,9 @@ def has_calls(self): # gh-100739 def test_mock_safe_with_spec(self): class Foo(object): - def assert_bar(self): - pass + def assert_bar(self): pass - def assertSome(self): - pass + def assertSome(self): pass m = Mock(spec=Foo) m.assert_bar() From 296d856f36e218cabf79f76009650562528a0146 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 9 Jan 2023 15:57:50 +0000 Subject: [PATCH 318/388] latest sync point --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index 107fd34e..fcdb1708 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -457c1f4a19a096a52d6553687c7c4cee415818dc +d3642c9a6a65d7edee2ea210d02a75a364df186e From 696fb23e7279ee285df22f8f988e7ac18bb7b356 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 9 Jan 2023 17:25:01 +0000 Subject: [PATCH 319/388] Backwards compatibility fix for 3.7 and earlier. --- mock/mock.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mock/mock.py b/mock/mock.py index bf292b42..5a0d70da 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2265,7 +2265,11 @@ def __init__(self, *args, **kwargs): ) code_mock.co_argcount = 0 code_mock.co_varnames = ('args', 'kwargs') - code_mock.co_posonlyargcount = 0 + try: + code_mock.co_posonlyargcount = 0 + except AttributeError: + # Python 3.7 and earlier. + pass code_mock.co_kwonlyargcount = 0 self.__dict__['__code__'] = code_mock self.__dict__['__name__'] = 'AsyncMock' From 921fad1bb3b781124bf27ee0187c6ce5d71652b0 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 9 Jan 2023 17:36:43 +0000 Subject: [PATCH 320/388] Preparing for 5.0.1 release. --- CHANGELOG.rst | 17 +++++++++++++++++ ...022-08-27-10-35-50.gh-issue-96127.8RdLre.rst | 2 -- ...23-01-02-16-59-49.gh-issue-100690.2EgWPS.rst | 7 ------- ...23-01-04-09-53-38.gh-issue-100740.-j5UjI.rst | 1 - mock/__init__.py | 2 +- 5 files changed, 18 insertions(+), 11 deletions(-) delete mode 100644 NEWS.d/2022-08-27-10-35-50.gh-issue-96127.8RdLre.rst delete mode 100644 NEWS.d/2023-01-02-16-59-49.gh-issue-100690.2EgWPS.rst delete mode 100644 NEWS.d/2023-01-04-09-53-38.gh-issue-100740.-j5UjI.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 386fb4cb..790285ed 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,20 @@ +5.0.1 +----- + +- gh-100740: Fix ``unittest.mock.Mock`` not respecting the spec for + attribute names prefixed with ``assert``. + +- gh-100690: ``Mock`` objects which are not unsafe will now raise an + ``AttributeError`` when accessing an attribute that matches the name of an + assertion but without the prefix ``assert_``, e.g. accessing + ``called_once`` instead of ``assert_called_once``. This is in addition to + this already happening for accessing attributes with prefixes ``assert``, + ``assret``, ``asert``, ``aseert``, and ``assrt``. + +- gh-96127: ``inspect.signature`` was raising ``TypeError`` on call with + mock objects. Now it correctly returns ``(*args, **kwargs)`` as infered + signature. + 5.0.0 ----- diff --git a/NEWS.d/2022-08-27-10-35-50.gh-issue-96127.8RdLre.rst b/NEWS.d/2022-08-27-10-35-50.gh-issue-96127.8RdLre.rst deleted file mode 100644 index 79edd8fd..00000000 --- a/NEWS.d/2022-08-27-10-35-50.gh-issue-96127.8RdLre.rst +++ /dev/null @@ -1,2 +0,0 @@ -``inspect.signature`` was raising ``TypeError`` on call with mock objects. -Now it correctly returns ``(*args, **kwargs)`` as infered signature. diff --git a/NEWS.d/2023-01-02-16-59-49.gh-issue-100690.2EgWPS.rst b/NEWS.d/2023-01-02-16-59-49.gh-issue-100690.2EgWPS.rst deleted file mode 100644 index 3796772a..00000000 --- a/NEWS.d/2023-01-02-16-59-49.gh-issue-100690.2EgWPS.rst +++ /dev/null @@ -1,7 +0,0 @@ -``Mock`` objects which are not unsafe will now raise an -``AttributeError`` when accessing an attribute that matches the name -of an assertion but without the prefix ``assert_``, e.g. accessing -``called_once`` instead of ``assert_called_once``. -This is in addition to this already happening for accessing attributes -with prefixes ``assert``, ``assret``, ``asert``, ``aseert``, -and ``assrt``. diff --git a/NEWS.d/2023-01-04-09-53-38.gh-issue-100740.-j5UjI.rst b/NEWS.d/2023-01-04-09-53-38.gh-issue-100740.-j5UjI.rst deleted file mode 100644 index 4753e7b4..00000000 --- a/NEWS.d/2023-01-04-09-53-38.gh-issue-100740.-j5UjI.rst +++ /dev/null @@ -1 +0,0 @@ -Fix ``unittest.mock.Mock`` not respecting the spec for attribute names prefixed with ``assert``. diff --git a/mock/__init__.py b/mock/__init__.py index 0e72ce6d..84d85df7 100644 --- a/mock/__init__.py +++ b/mock/__init__.py @@ -7,7 +7,7 @@ import mock.mock as _mock from mock.mock import * -__version__ = '5.0.0' +__version__ = '5.0.1' version_info = tuple(int(p) for p in re.match(r'(\d+).(\d+).(\d+)', __version__).groups()) From e132217e92a65f4c69657f578d6802422777b3ca Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 16 Apr 2023 10:48:36 +0100 Subject: [PATCH 321/388] Actually use pypy to run pypy's CI --- .circleci/config.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d55642ab..4ad9da48 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,7 +1,7 @@ version: 2.1 orbs: - python: cjw296/python-ci@3 + python: cjw296/python-ci@3.3 jobs: check-package: @@ -27,6 +27,7 @@ common: &common - python/pip-run-tests: matrix: + alias: python-tests parameters: image: - cimg/python:3.6 @@ -35,12 +36,17 @@ common: &common - cimg/python:3.9 - cimg/python:3.10 - cimg/python:3.11 - - pypy:3.7-7.3.2 # https://foss.heptapod.net/pypy/pypy/-/issues/3436 + + - python/pip-run-tests: + name: pypy-tests + python: pypy3 + image: pypy:3.7-7.3.2 # https://foss.heptapod.net/pypy/pypy/-/issues/3436 - python/coverage: name: coverage requires: - - python/pip-run-tests + - python-tests + - pypy-tests - python/pip-docs: name: docs From 4145e0fde584601cdb17a4c0229d58f05479c2db Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 16 Apr 2023 10:58:24 +0100 Subject: [PATCH 322/388] Additionally test on pypy 3.9 / 7.3.11 --- .circleci/config.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4ad9da48..47ed0e3a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -38,9 +38,13 @@ common: &common - cimg/python:3.11 - python/pip-run-tests: - name: pypy-tests python: pypy3 - image: pypy:3.7-7.3.2 # https://foss.heptapod.net/pypy/pypy/-/issues/3436 + matrix: + alias: pypy-tests + parameters: + image: + - pypy:3.7-7.3.2 + - pypy:latest - python/coverage: name: coverage From 09b0b2932e1e5722d5a24e058797131e50238081 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 16 Apr 2023 11:14:53 +0100 Subject: [PATCH 323/388] correct lastsync.txt revision The previous one was from the PR, not the resulting squash-merge --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index fcdb1708..e3233552 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -d3642c9a6a65d7edee2ea210d02a75a364df186e +4e544eafcb603babe0db01270bd1c6d5d0f5d6ea From 1c7bfeff6575a5fee073bb648126780e00923936 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 8 Apr 2023 10:09:00 +0800 Subject: [PATCH 324/388] gh-103329: Add regression test for PropertyMock with side effect (#103358) Backports: 26c65980dc6d842879d133165bb7c461d98cc6c7 Signed-off-by: Chris Withers --- ...-04-08-00-50-23.gh-issue-103329.M38tqF.rst | 1 + mock/tests/testhelpers.py | 23 ++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 NEWS.d/2023-04-08-00-50-23.gh-issue-103329.M38tqF.rst diff --git a/NEWS.d/2023-04-08-00-50-23.gh-issue-103329.M38tqF.rst b/NEWS.d/2023-04-08-00-50-23.gh-issue-103329.M38tqF.rst new file mode 100644 index 00000000..79448ed7 --- /dev/null +++ b/NEWS.d/2023-04-08-00-50-23.gh-issue-103329.M38tqF.rst @@ -0,0 +1 @@ +Regression tests for the behaviour of ``unittest.mock.PropertyMock`` were added. diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index 2a28796f..0fcc4f27 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -1086,7 +1086,7 @@ def test_propertymock(self): p.stop() - def test_propertymock_returnvalue(self): + def test_propertymock_bare(self): m = MagicMock() p = PropertyMock() type(m).foo = p @@ -1097,6 +1097,27 @@ def test_propertymock_returnvalue(self): self.assertNotIsInstance(returned, PropertyMock) + def test_propertymock_returnvalue(self): + m = MagicMock() + p = PropertyMock(return_value=42) + type(m).foo = p + + returned = m.foo + p.assert_called_once_with() + self.assertEqual(returned, 42) + self.assertNotIsInstance(returned, PropertyMock) + + + def test_propertymock_side_effect(self): + m = MagicMock() + p = PropertyMock(side_effect=ValueError) + type(m).foo = p + + with self.assertRaises(ValueError): + m.foo + p.assert_called_once_with() + + class TestCallablePredicate(unittest.TestCase): def test_type(self): From 079655cd9da2fd5fc22126b422ef3c100a3f96a2 Mon Sep 17 00:00:00 2001 From: Tomas R Date: Thu, 13 Apr 2023 09:37:57 +0200 Subject: [PATCH 325/388] gh-102978: Fix mock.patch function signatures for class and staticmethod decorators (#103228) Fixes unittest.mock.patch not enforcing function signatures for methods decorated with @classmethod or @staticmethod when patch is called with autospec=True. Backports: 59e0de4903c02e72b329e505fddf1ad9794928bc Signed-off-by: Chris Withers --- ...-04-03-23-44-34.gh-issue-102978.gy9eVk.rst | 3 ++ mock/mock.py | 6 ++++ mock/tests/testhelpers.py | 18 +++++++++++ mock/tests/testpatch.py | 30 +++++++++++++++++++ 4 files changed, 57 insertions(+) create mode 100644 NEWS.d/2023-04-03-23-44-34.gh-issue-102978.gy9eVk.rst diff --git a/NEWS.d/2023-04-03-23-44-34.gh-issue-102978.gy9eVk.rst b/NEWS.d/2023-04-03-23-44-34.gh-issue-102978.gy9eVk.rst new file mode 100644 index 00000000..df63af10 --- /dev/null +++ b/NEWS.d/2023-04-03-23-44-34.gh-issue-102978.gy9eVk.rst @@ -0,0 +1,3 @@ +Fixes :func:`unittest.mock.patch` not enforcing function signatures for methods +decorated with ``@classmethod`` or ``@staticmethod`` when patch is called with +``autospec=True``. diff --git a/mock/mock.py b/mock/mock.py index 5a0d70da..d07e044f 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -101,6 +101,12 @@ def _get_signature_object(func, as_instance, eat_self): func = func.__init__ # Skip the `self` argument in __init__ eat_self = True + elif isinstance(func, (classmethod, staticmethod)): + if isinstance(func, classmethod): + # Skip the `cls` argument of a class method + eat_self = True + # Use the original decorated method to extract the correct function signature + func = func.__func__ elif not isinstance(func, FunctionTypes): # If we really want to model an instance of the passed type, # __call__ should be looked up, not __init__. diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index 0fcc4f27..def8450e 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -961,6 +961,24 @@ def __getattr__(self, attribute): self.assertFalse(hasattr(autospec, '__name__')) + def test_autospec_signature_staticmethod(self): + class Foo: + @staticmethod + def static_method(a, b=10, *, c): pass + + mock = create_autospec(Foo.__dict__['static_method']) + self.assertEqual(inspect.signature(Foo.static_method), inspect.signature(mock)) + + + def test_autospec_signature_classmethod(self): + class Foo: + @classmethod + def class_method(cls, a, b=10, *, c): pass + + mock = create_autospec(Foo.__dict__['class_method']) + self.assertEqual(inspect.signature(Foo.class_method), inspect.signature(mock)) + + def test_spec_inspect_signature(self): def myfunc(x, y): pass diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index 3e4c29e0..e15e9a22 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -996,6 +996,36 @@ def test_autospec_classmethod(self): method.assert_called_once_with() + def test_autospec_staticmethod_signature(self): + # Patched methods which are decorated with @staticmethod should have the same signature + class Foo: + @staticmethod + def static_method(a, b=10, *, c): pass + + Foo.static_method(1, 2, c=3) + + with patch.object(Foo, 'static_method', autospec=True) as method: + method(1, 2, c=3) + self.assertRaises(TypeError, method) + self.assertRaises(TypeError, method, 1) + self.assertRaises(TypeError, method, 1, 2, 3, c=4) + + + def test_autospec_classmethod_signature(self): + # Patched methods which are decorated with @classmethod should have the same signature + class Foo: + @classmethod + def class_method(cls, a, b=10, *, c): pass + + Foo.class_method(1, 2, c=3) + + with patch.object(Foo, 'class_method', autospec=True) as method: + method(1, 2, c=3) + self.assertRaises(TypeError, method) + self.assertRaises(TypeError, method, 1) + self.assertRaises(TypeError, method, 1, 2, 3, c=4) + + def test_autospec_with_new(self): patcher = patch('%s.function' % __name__, new=3, autospec=True) self.assertRaises(TypeError, patcher.start) From b8a7dedd43e752ea96c9edf47f80e639e79ed41f Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 16 Apr 2023 11:15:47 +0100 Subject: [PATCH 326/388] latest sync point --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index e3233552..44e73d43 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -4e544eafcb603babe0db01270bd1c6d5d0f5d6ea +59e0de4903c02e72b329e505fddf1ad9794928bc From 03a3f120594b93c641fed92c60f673edbfde1401 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sun, 16 Apr 2023 12:24:57 +0100 Subject: [PATCH 327/388] Preparing for 5.0.2 release. --- CHANGELOG.rst | 10 ++++++++++ NEWS.d/2023-04-03-23-44-34.gh-issue-102978.gy9eVk.rst | 3 --- NEWS.d/2023-04-08-00-50-23.gh-issue-103329.M38tqF.rst | 1 - mock/__init__.py | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) delete mode 100644 NEWS.d/2023-04-03-23-44-34.gh-issue-102978.gy9eVk.rst delete mode 100644 NEWS.d/2023-04-08-00-50-23.gh-issue-103329.M38tqF.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 790285ed..3fa5887e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,13 @@ +5.0.2 +----- + +- gh-102978: Fixes :func:`unittest.mock.patch` not enforcing function + signatures for methods decorated with ``@classmethod`` or + ``@staticmethod`` when patch is called with ``autospec=True``. + +- gh-103329: Regression tests for the behaviour of + ``unittest.mock.PropertyMock`` were added. + 5.0.1 ----- diff --git a/NEWS.d/2023-04-03-23-44-34.gh-issue-102978.gy9eVk.rst b/NEWS.d/2023-04-03-23-44-34.gh-issue-102978.gy9eVk.rst deleted file mode 100644 index df63af10..00000000 --- a/NEWS.d/2023-04-03-23-44-34.gh-issue-102978.gy9eVk.rst +++ /dev/null @@ -1,3 +0,0 @@ -Fixes :func:`unittest.mock.patch` not enforcing function signatures for methods -decorated with ``@classmethod`` or ``@staticmethod`` when patch is called with -``autospec=True``. diff --git a/NEWS.d/2023-04-08-00-50-23.gh-issue-103329.M38tqF.rst b/NEWS.d/2023-04-08-00-50-23.gh-issue-103329.M38tqF.rst deleted file mode 100644 index 79448ed7..00000000 --- a/NEWS.d/2023-04-08-00-50-23.gh-issue-103329.M38tqF.rst +++ /dev/null @@ -1 +0,0 @@ -Regression tests for the behaviour of ``unittest.mock.PropertyMock`` were added. diff --git a/mock/__init__.py b/mock/__init__.py index 84d85df7..85abb93e 100644 --- a/mock/__init__.py +++ b/mock/__init__.py @@ -7,7 +7,7 @@ import mock.mock as _mock from mock.mock import * -__version__ = '5.0.1' +__version__ = '5.0.2' version_info = tuple(int(p) for p in re.match(r'(\d+).(\d+).(\d+)', __version__).groups()) From 5db11ade2dd0589e8063b1528d83b3dd7b1e0f6a Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 3 May 2023 08:20:16 +0100 Subject: [PATCH 328/388] Update orb version for Sphinx 7 --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 47ed0e3a..3bd8814a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,7 +1,7 @@ version: 2.1 orbs: - python: cjw296/python-ci@3.3 + python: cjw296/python-ci@3.4 jobs: check-package: From 0bd403767e3c3115e7d7b20b8b955d51054ca81a Mon Sep 17 00:00:00 2001 From: melanie witt Date: Tue, 23 May 2023 16:10:34 -0700 Subject: [PATCH 329/388] gh-85934: Use getattr_static when adding mock spec (#22209) Co-authored-by: Terry Jan Reedy Co-authored-by: Oleg Iarygin Backports: 2e0931046dcc200fd6abb2cdfaf57d8b99117c57 Signed-off-by: Chris Withers --- .../2020-09-16-16-53-06.bpo-41768.8_fWkC.rst | 2 ++ mock/mock.py | 8 ++++- mock/tests/testmock.py | 31 +++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 NEWS.d/2020-09-16-16-53-06.bpo-41768.8_fWkC.rst diff --git a/NEWS.d/2020-09-16-16-53-06.bpo-41768.8_fWkC.rst b/NEWS.d/2020-09-16-16-53-06.bpo-41768.8_fWkC.rst new file mode 100644 index 00000000..bfd3a294 --- /dev/null +++ b/NEWS.d/2020-09-16-16-53-06.bpo-41768.8_fWkC.rst @@ -0,0 +1,2 @@ +:mod:`unittest.mock` speccing no longer calls class properties. +Patch by Melanie Witt. diff --git a/mock/mock.py b/mock/mock.py index d07e044f..a8976dd0 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -529,7 +529,13 @@ def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False, spec_list = dir(spec) for attr in spec_list: - if iscoroutinefunction(getattr(spec, attr, None)): + static_attr = inspect.getattr_static(spec, attr, None) + unwrapped_attr = static_attr + try: + unwrapped_attr = inspect.unwrap(unwrapped_attr) + except ValueError: + pass + if iscoroutinefunction(unwrapped_attr): _spec_asyncs.append(attr) spec = spec_list diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index f6db14ae..9ca3b992 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -39,6 +39,17 @@ def cmeth(cls, a, b, c, d=None): pass def smeth(a, b, c, d=None): pass +class SomethingElse(object): + def __init__(self): + self._instance = None + + @property + def instance(self): + if not self._instance: + self._instance = 'object' + return self._instance + + class Typos(): autospect = None auto_spec = None @@ -2296,6 +2307,26 @@ class Foo(): f'{__name__}.Typos', autospect=True, set_spec=True, auto_spec=True): pass + def test_property_not_called_with_spec_mock(self): + obj = SomethingElse() + self.assertIsNone(obj._instance, msg='before mock') + mock = Mock(spec=obj) + self.assertIsNone(obj._instance, msg='after mock') + self.assertEqual('object', obj.instance) + + def test_decorated_async_methods_with_spec_mock(self): + class Foo(): + @classmethod + async def class_method(cls): + pass + @staticmethod + async def static_method(): + pass + async def method(self): + pass + mock = Mock(spec=Foo) + for m in (mock.method, mock.class_method, mock.static_method): + self.assertIsInstance(m, AsyncMock) if __name__ == '__main__': unittest.main() From ad441f7d6e176dc75069f6be1e4bc7c7255a8de9 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Tue, 30 May 2023 10:36:22 +0300 Subject: [PATCH 330/388] gh-83403: Test `parent` param in `Mock.__init__` (#103630) Backports: 219f01b18574469f493a3d3cb91d96c2f057218c Signed-off-by: Chris Withers --- mock/tests/testmock.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 9ca3b992..15846224 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -246,6 +246,14 @@ class B(object): with mock.patch('builtins.open', mock.mock_open()): mock.mock_open() # should still be valid with open() mocked + def test_explicit_parent(self): + parent = Mock() + mock1 = Mock(parent=parent, return_value=None) + mock1(1, 2, 3) + mock2 = Mock(parent=parent, return_value=None) + mock2(4, 5, 6) + + self.assertEqual(parent.mock_calls, [call(1, 2, 3), call(4, 5, 6)]) def test_reset_mock(self): parent = Mock() From f8cefdbcd82f8780b811c69d9e305edd2df35b8b Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Fri, 9 Jun 2023 09:04:55 -0400 Subject: [PATCH 331/388] latest sync point --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index 44e73d43..1e6278ce 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -59e0de4903c02e72b329e505fddf1ad9794928bc +219f01b18574469f493a3d3cb91d96c2f057218c From 8a48793ed626cb7af42f99223ad3099caa55fd91 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 12 Jun 2023 09:16:31 +0100 Subject: [PATCH 332/388] Change test expectation for Python 3.9 and earlier Bugfixes outside the mock library are needed and no sensible route to backport --- mock/tests/testmock.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 15846224..344813c0 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -2334,6 +2334,10 @@ async def method(self): pass mock = Mock(spec=Foo) for m in (mock.method, mock.class_method, mock.static_method): + if sys.version_info[:2] <= (3, 9) and m is not mock.method: + # class and static methods need bugfixes in cpython to work: + self.assertIsInstance(m, Mock) + continue self.assertIsInstance(m, AsyncMock) if __name__ == '__main__': From 940a78403ad7f15be78a24ef7b027f3bc196878f Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 12 Jun 2023 09:20:51 +0100 Subject: [PATCH 333/388] Less ugly plan for code never expected to execute --- docs/index.txt | 6 ++++-- mock/tests/testmock.py | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/index.txt b/docs/index.txt index 06d14b6c..1aee3f71 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -115,7 +115,8 @@ Backporting rules .. code-block:: python - def will_never_be_called(): pass + def will_never_be_called(): + pass # pragma: no cov - If code such as this causes coverage checking to drop below 100%: @@ -129,7 +130,8 @@ Backporting rules .. code-block:: python - def will_never_be_called(): yield + def will_never_be_called(): + yield # pragma: no cover - If a backported patch applies cleanly, but ends up needing to be skipped, then commit the latest sync point and then revert the problematic commit in an immediately diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 344813c0..796e20e4 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -2326,12 +2326,12 @@ def test_decorated_async_methods_with_spec_mock(self): class Foo(): @classmethod async def class_method(cls): - pass + pass # pragma: no cover @staticmethod async def static_method(): - pass + pass # pragma: no cover async def method(self): - pass + pass # pragma: no cover mock = Mock(spec=Foo) for m in (mock.method, mock.class_method, mock.static_method): if sys.version_info[:2] <= (3, 9) and m is not mock.method: From 46733c6e7dd35acddb4fe9c22bb4663b6947f87a Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 10 Jul 2023 09:15:15 +0100 Subject: [PATCH 334/388] typo --- docs/index.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.txt b/docs/index.txt index 1aee3f71..a74a26c7 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -135,7 +135,7 @@ Backporting rules - If a backported patch applies cleanly, but ends up needing to be skipped, then commit the latest sync point and then revert the problematic commit in an immediately - subsequent commit and make a not of the reason for the revert in that commit message. + subsequent commit and make a note of the reason for the revert in that commit message. See `bc04ea7`__ for an example where `f4c8dc7`__ broke compatibility for all Python versions earlier than 3.10. From 6f79656c23efe89180f50677185c8430a1ee46de Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Fri, 9 Jun 2023 14:29:09 +0100 Subject: [PATCH 335/388] gh-94924: support `inspect.iscoroutinefunction` in `create_autospec(async_def)` (#94962) * support inspect.iscoroutinefunction in create_autospec(async_def) * test create_autospec with inspect.iscoroutine and inspect.iscoroutinefunction * test when create_autospec functions check their signature Backports: 9bf8d825a66ea2a76169b917c12c237a6af2ed75 Signed-off-by: Chris Withers --- ...2-07-18-14-20-56.gh-issue-94924.X0buz2.rst | 1 + mock/mock.py | 32 +++++++++++++++++-- mock/tests/testasync.py | 23 +++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 NEWS.d/2022-07-18-14-20-56.gh-issue-94924.X0buz2.rst diff --git a/NEWS.d/2022-07-18-14-20-56.gh-issue-94924.X0buz2.rst b/NEWS.d/2022-07-18-14-20-56.gh-issue-94924.X0buz2.rst new file mode 100644 index 00000000..7882f224 --- /dev/null +++ b/NEWS.d/2022-07-18-14-20-56.gh-issue-94924.X0buz2.rst @@ -0,0 +1 @@ +:func:`unittest.mock.create_autospec` now properly returns coroutine functions compatible with :func:`inspect.iscoroutinefunction` diff --git a/mock/mock.py b/mock/mock.py index a8976dd0..806a242f 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -207,6 +207,33 @@ def checksig(*args, **kwargs): _setup_func(funcopy, mock, sig) return funcopy +def _set_async_signature(mock, original, instance=False, is_async_mock=False): + # creates an async function with signature (*args, **kwargs) that delegates to a + # mock. It still does signature checking by calling a lambda with the same + # signature as the original. + + skipfirst = isinstance(original, type) + result = _get_signature_object(original, instance, skipfirst) + if result is None: + return mock + func, sig = result + def checksig(*args, **kwargs): + sig.bind(*args, **kwargs) + _copy_func_details(func, checksig) + + name = original.__name__ + if not name.isidentifier(): + name = 'funcopy' + context = {'_checksig_': checksig, 'mock': mock} + src = """async def %s(*args, **kwargs): + _checksig_(*args, **kwargs) + return await mock(*args, **kwargs)""" % name + exec (src, context) + funcopy = context[name] + _setup_func(funcopy, mock, sig) + _setup_async_mock(funcopy) + return funcopy + def _setup_func(funcopy, mock, sig): funcopy.mock = mock @@ -2798,9 +2825,10 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, if isinstance(spec, FunctionTypes): # 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) + mock = _set_async_signature(mock, spec) + else: + mock = _set_signature(mock, spec) else: _check_signature(spec, mock, is_type, instance) diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index 41c22aca..7fd680f4 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -242,7 +242,9 @@ async def main(): run(main()) self.assertTrue(iscoroutinefunction(spec)) + self.assertTrue(inspect.iscoroutinefunction(spec)) self.assertTrue(asyncio.iscoroutine(awaitable)) + self.assertTrue(inspect.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)]) @@ -254,6 +256,25 @@ async def main(): with self.assertRaises(AssertionError): spec.assert_any_await(e=1) + def test_autospec_checks_signature(self): + spec = create_autospec(async_func_args) + # signature is not checked when called + awaitable = spec() + self.assertListEqual(spec.mock_calls, []) + + async def main(): + await awaitable + + # but it is checked when awaited + with self.assertRaises(TypeError): + run(main()) + + # _checksig_ raises before running or awaiting the mock + self.assertListEqual(spec.mock_calls, []) + self.assertEqual(spec.await_count, 0) + self.assertIsNone(spec.await_args) + self.assertEqual(spec.await_args_list, []) + spec.assert_not_awaited() def test_patch_with_autospec(self): @@ -263,7 +284,9 @@ async def test_async(): self.assertIsInstance(mock_method.mock, AsyncMock) self.assertTrue(iscoroutinefunction(mock_method)) + self.assertTrue(inspect.iscoroutinefunction(mock_method)) self.assertTrue(asyncio.iscoroutine(awaitable)) + self.assertTrue(inspect.iscoroutine(awaitable)) self.assertTrue(inspect.isawaitable(awaitable)) # Verify the default values during mock setup From 0bd94e622397bb2245a951086adb450731ee5d63 Mon Sep 17 00:00:00 2001 From: Samet YASLAN Date: Sun, 11 Jun 2023 20:51:21 +0200 Subject: [PATCH 336/388] bpo-44185: Added close() to mock_open __exit__ (#26902) Backports: 3f7c0810f6158a7ff37be432f8d7f9511427489f Signed-off-by: Chris Withers --- NEWS.d/2021-06-24-20-45-03.bpo-44185.ZHb8yJ.rst | 3 +++ mock/mock.py | 4 ++++ mock/tests/testwith.py | 6 +++--- 3 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 NEWS.d/2021-06-24-20-45-03.bpo-44185.ZHb8yJ.rst diff --git a/NEWS.d/2021-06-24-20-45-03.bpo-44185.ZHb8yJ.rst b/NEWS.d/2021-06-24-20-45-03.bpo-44185.ZHb8yJ.rst new file mode 100644 index 00000000..056ab8d9 --- /dev/null +++ b/NEWS.d/2021-06-24-20-45-03.bpo-44185.ZHb8yJ.rst @@ -0,0 +1,3 @@ +:func:`unittest.mock.mock_open` will call the :func:`close` method of the file +handle mock when it is exiting from the context manager. +Patch by Samet Yaslan. diff --git a/mock/mock.py b/mock/mock.py index 806a242f..c7a98f4e 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2994,6 +2994,9 @@ def _next_side_effect(): return handle.readline.return_value return next(_state[0]) + def _exit_side_effect(exctype, excinst, exctb): + handle.close() + global file_spec if file_spec is None: import _io @@ -3020,6 +3023,7 @@ def _next_side_effect(): handle.readlines.side_effect = _readlines_side_effect handle.__iter__.side_effect = _iter_side_effect handle.__next__.side_effect = _next_side_effect + handle.__exit__.side_effect = _exit_side_effect def reset_data(*args, **kwargs): _state[0] = _to_stream(read_data) diff --git a/mock/tests/testwith.py b/mock/tests/testwith.py index 34326f5d..26c63a2d 100644 --- a/mock/tests/testwith.py +++ b/mock/tests/testwith.py @@ -158,7 +158,7 @@ def test_mock_open_context_manager(self): f.read() expected_calls = [call('foo'), call().__enter__(), call().read(), - call().__exit__(None, None, None)] + call().__exit__(None, None, None), call().close()] self.assertEqual(mock.mock_calls, expected_calls) self.assertIs(f, handle) @@ -172,9 +172,9 @@ def test_mock_open_context_manager_multiple_times(self): expected_calls = [ call('foo'), call().__enter__(), call().read(), - call().__exit__(None, None, None), + call().__exit__(None, None, None), call().close(), call('bar'), call().__enter__(), call().read(), - call().__exit__(None, None, None)] + call().__exit__(None, None, None), call().close()] self.assertEqual(mock.mock_calls, expected_calls) def test_explicit_mock(self): From cb8b3c8c8a3f1661f93b989440853b9377a91303 Mon Sep 17 00:00:00 2001 From: Mario Corchero Date: Mon, 3 Jul 2023 07:56:54 +0100 Subject: [PATCH 337/388] gh-61215: New mock to wait for multi-threaded events to happen (#16094) mock: Add `ThreadingMock` class Add a new class that allows to wait for a call to happen by using `Event` objects. This mock class can be used to test and validate expectations of multithreading code. It uses two attributes for events to distinguish calls with any argument and calls with specific arguments. The calls with specific arguments need a lock to prevent two calls in parallel from creating the same event twice. The timeout is configured at class and constructor level to allow users to set a timeout, we considered passing it as an argument to the function but it could collide with a function parameter. Alternatively we also considered passing it as positional only but from an API caller perspective it was unclear what the first number meant on the function call, think `mock.wait_until_called(1, "arg1", "arg2")`, where 1 is the timeout. Lastly we also considered adding the new attributes to magic mock directly rather than having a custom mock class for multi threading scenarios, but we preferred to have specialised class that can be composed if necessary. Additionally, having added it to `MagicMock` directly would have resulted in `AsyncMock` having this logic, which would not work as expected, since when if user "waits" on a coroutine does not have the same meaning as waiting on a standard call. Co-authored-by: Karthikeyan Singaravelan Backports: d65b783b6966d233467a48ef633afb4aff9d5df8 Signed-off-by: Chris Withers --- .../2019-09-13-13-28-10.bpo-17013.NWcgE3.rst | 3 + lastsync.txt | 2 +- mock/mock.py | 95 +++++- mock/tests/testthreadingmock.py | 278 ++++++++++++++++++ 4 files changed, 376 insertions(+), 2 deletions(-) create mode 100644 NEWS.d/2019-09-13-13-28-10.bpo-17013.NWcgE3.rst create mode 100644 mock/tests/testthreadingmock.py diff --git a/NEWS.d/2019-09-13-13-28-10.bpo-17013.NWcgE3.rst b/NEWS.d/2019-09-13-13-28-10.bpo-17013.NWcgE3.rst new file mode 100644 index 00000000..ac746c45 --- /dev/null +++ b/NEWS.d/2019-09-13-13-28-10.bpo-17013.NWcgE3.rst @@ -0,0 +1,3 @@ +Add ``ThreadingMock`` to :mod:`unittest.mock` that can be used to create +Mock objects that can wait until they are called. Patch by Karthikeyan +Singaravelan and Mario Corchero. diff --git a/lastsync.txt b/lastsync.txt index 1e6278ce..2e06272f 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -219f01b18574469f493a3d3cb91d96c2f057218c +3f7c0810f6158a7ff37be432f8d7f9511427489f diff --git a/mock/mock.py b/mock/mock.py index c7a98f4e..59793645 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -14,6 +14,7 @@ 'call', 'create_autospec', 'AsyncMock', + 'ThreadingMock', 'FILTER_DIR', 'NonCallableMock', 'NonCallableMagicMock', @@ -29,8 +30,8 @@ import inspect import pprint import sys +import threading import builtins -from asyncio import iscoroutinefunction from types import CodeType, ModuleType, MethodType from unittest.util import safe_repr from functools import wraps, partial @@ -3056,6 +3057,98 @@ def __set__(self, obj, val): self(val) +_timeout_unset = sentinel.TIMEOUT_UNSET + +class ThreadingMixin(Base): + + DEFAULT_TIMEOUT = None + + def _get_child_mock(self, /, **kw): + if "timeout" in kw: + kw["timeout"] = kw.pop("timeout") + elif isinstance(kw.get("parent"), ThreadingMixin): + kw["timeout"] = kw["parent"]._mock_wait_timeout + elif isinstance(kw.get("_new_parent"), ThreadingMixin): + kw["timeout"] = kw["_new_parent"]._mock_wait_timeout + return super()._get_child_mock(**kw) + + def __init__(self, *args, timeout=_timeout_unset, **kwargs): + super().__init__(*args, **kwargs) + if timeout is _timeout_unset: + timeout = self.DEFAULT_TIMEOUT + self.__dict__["_mock_event"] = threading.Event() # Event for any call + self.__dict__["_mock_calls_events"] = [] # Events for each of the calls + self.__dict__["_mock_calls_events_lock"] = threading.Lock() + self.__dict__["_mock_wait_timeout"] = timeout + + def reset_mock(self, /, *args, **kwargs): + """ + See :func:`.Mock.reset_mock()` + """ + super().reset_mock(*args, **kwargs) + self.__dict__["_mock_event"] = threading.Event() + self.__dict__["_mock_calls_events"] = [] + + def __get_event(self, expected_args, expected_kwargs): + with self._mock_calls_events_lock: + for args, kwargs, event in self._mock_calls_events: + if (args, kwargs) == (expected_args, expected_kwargs): + return event + new_event = threading.Event() + self._mock_calls_events.append((expected_args, expected_kwargs, new_event)) + return new_event + + def _mock_call(self, *args, **kwargs): + ret_value = super()._mock_call(*args, **kwargs) + + call_event = self.__get_event(args, kwargs) + call_event.set() + + self._mock_event.set() + + return ret_value + + def wait_until_called(self, *, timeout=_timeout_unset): + """Wait until the mock object is called. + + `timeout` - time to wait for in seconds, waits forever otherwise. + Defaults to the constructor provided timeout. + Use None to block undefinetively. + """ + if timeout is _timeout_unset: + timeout = self._mock_wait_timeout + if not self._mock_event.wait(timeout=timeout): + msg = (f"{self._mock_name or 'mock'} was not called before" + f" timeout({timeout}).") + raise AssertionError(msg) + + def wait_until_any_call(self, *args, **kwargs): + """Wait until the mock object is called with given args. + + Waits for the timeout in seconds provided in the constructor. + """ + event = self.__get_event(args, kwargs) + if not event.wait(timeout=self._mock_wait_timeout): + expected_string = self._format_mock_call_signature(args, kwargs) + raise AssertionError(f'{expected_string} call not found') + + +class ThreadingMock(ThreadingMixin, MagicMixin, Mock): + """ + A mock that can be used to wait until on calls happening + in a different thread. + + The constructor can take a `timeout` argument which + controls the timeout in seconds for all `wait` calls of the mock. + + You can change the default timeout of all instances via the + `ThreadingMock.DEFAULT_TIMEOUT` attribute. + + If no timeout is set, it will block undefinetively. + """ + pass + + def seal(mock): """Disable the automatic generation of child mocks. diff --git a/mock/tests/testthreadingmock.py b/mock/tests/testthreadingmock.py new file mode 100644 index 00000000..7a9072dd --- /dev/null +++ b/mock/tests/testthreadingmock.py @@ -0,0 +1,278 @@ +import time +import unittest +import concurrent.futures + +from mock import patch, ThreadingMock, call + + +class Something: + def method_1(self): + pass + + def method_2(self): + pass + + +class TestThreadingMock(unittest.TestCase): + def _call_after_delay(self, func, /, *args, **kwargs): + time.sleep(kwargs.pop("delay")) + func(*args, **kwargs) + + def setUp(self): + self._executor = concurrent.futures.ThreadPoolExecutor(max_workers=5) + + def tearDown(self): + self._executor.shutdown() + + def run_async(self, func, /, *args, delay=0, **kwargs): + self._executor.submit( + self._call_after_delay, func, *args, **kwargs, delay=delay + ) + + def _make_mock(self, *args, **kwargs): + return ThreadingMock(*args, **kwargs) + + def test_spec(self): + waitable_mock = self._make_mock(spec=Something) + + with patch(f"{__name__}.Something", waitable_mock) as m: + something = m() + + self.assertIsInstance(something.method_1, ThreadingMock) + self.assertIsInstance(something.method_1().method_2(), ThreadingMock) + + with self.assertRaises(AttributeError): + m.test + + def test_side_effect(self): + waitable_mock = self._make_mock() + + with patch(f"{__name__}.Something", waitable_mock): + something = Something() + something.method_1.side_effect = [1] + + self.assertEqual(something.method_1(), 1) + + def test_instance_check(self): + waitable_mock = self._make_mock() + + with patch(f"{__name__}.Something", waitable_mock): + something = Something() + + self.assertIsInstance(something.method_1, ThreadingMock) + self.assertIsInstance(something.method_1().method_2(), ThreadingMock) + + def test_dynamic_child_mocks_are_threading_mocks(self): + waitable_mock = self._make_mock() + self.assertIsInstance(waitable_mock.child, ThreadingMock) + + def test_dynamic_child_mocks_inherit_timeout(self): + mock1 = self._make_mock() + self.assertIs(mock1._mock_wait_timeout, None) + mock2 = self._make_mock(timeout=2) + self.assertEqual(mock2._mock_wait_timeout, 2) + mock3 = self._make_mock(timeout=3) + self.assertEqual(mock3._mock_wait_timeout, 3) + + self.assertIs(mock1.child._mock_wait_timeout, None) + self.assertEqual(mock2.child._mock_wait_timeout, 2) + self.assertEqual(mock3.child._mock_wait_timeout, 3) + + self.assertEqual(mock2.really().__mul__().complex._mock_wait_timeout, 2) + + def test_no_name_clash(self): + waitable_mock = self._make_mock() + waitable_mock._event = "myevent" + waitable_mock.event = "myevent" + waitable_mock.timeout = "mytimeout" + waitable_mock("works") + waitable_mock.wait_until_called() + waitable_mock.wait_until_any_call("works") + + def test_wait_success(self): + waitable_mock = self._make_mock(spec=Something) + + with patch(f"{__name__}.Something", waitable_mock): + something = Something() + self.run_async(something.method_1, delay=0.01) + something.method_1.wait_until_called() + something.method_1.wait_until_any_call() + something.method_1.assert_called() + + def test_wait_success_with_instance_timeout(self): + waitable_mock = self._make_mock(timeout=1) + + with patch(f"{__name__}.Something", waitable_mock): + something = Something() + self.run_async(something.method_1, delay=0.01) + something.method_1.wait_until_called() + something.method_1.wait_until_any_call() + something.method_1.assert_called() + + def test_wait_failed_with_instance_timeout(self): + waitable_mock = self._make_mock(timeout=0.01) + + with patch(f"{__name__}.Something", waitable_mock): + something = Something() + self.run_async(something.method_1, delay=0.5) + self.assertRaises(AssertionError, waitable_mock.method_1.wait_until_called) + self.assertRaises( + AssertionError, waitable_mock.method_1.wait_until_any_call + ) + + def test_wait_success_with_timeout_override(self): + waitable_mock = self._make_mock(timeout=0.01) + + with patch(f"{__name__}.Something", waitable_mock): + something = Something() + self.run_async(something.method_1, delay=0.05) + something.method_1.wait_until_called(timeout=1) + + def test_wait_failed_with_timeout_override(self): + waitable_mock = self._make_mock(timeout=1) + + with patch(f"{__name__}.Something", waitable_mock): + something = Something() + self.run_async(something.method_1, delay=0.1) + with self.assertRaises(AssertionError): + something.method_1.wait_until_called(timeout=0.05) + with self.assertRaises(AssertionError): + something.method_1.wait_until_any_call(timeout=0.05) + + def test_wait_success_called_before(self): + waitable_mock = self._make_mock() + + with patch(f"{__name__}.Something", waitable_mock): + something = Something() + something.method_1() + something.method_1.wait_until_called() + something.method_1.wait_until_any_call() + something.method_1.assert_called() + + def test_wait_magic_method(self): + waitable_mock = self._make_mock() + + with patch(f"{__name__}.Something", waitable_mock): + something = Something() + self.run_async(something.method_1.__str__, delay=0.01) + something.method_1.__str__.wait_until_called() + something.method_1.__str__.assert_called() + + def test_wait_until_any_call_positional(self): + waitable_mock = self._make_mock(spec=Something) + + with patch(f"{__name__}.Something", waitable_mock): + something = Something() + self.run_async(something.method_1, 1, delay=0.1) + self.run_async(something.method_1, 2, delay=0.2) + self.run_async(something.method_1, 3, delay=0.3) + self.assertNotIn(call(1), something.method_1.mock_calls) + + something.method_1.wait_until_any_call(1) + something.method_1.assert_called_with(1) + self.assertNotIn(call(2), something.method_1.mock_calls) + self.assertNotIn(call(3), something.method_1.mock_calls) + + something.method_1.wait_until_any_call(3) + self.assertIn(call(2), something.method_1.mock_calls) + something.method_1.wait_until_any_call(2) + + def test_wait_until_any_call_keywords(self): + waitable_mock = self._make_mock(spec=Something) + + with patch(f"{__name__}.Something", waitable_mock): + something = Something() + self.run_async(something.method_1, a=1, delay=0.1) + self.run_async(something.method_1, b=2, delay=0.2) + self.run_async(something.method_1, c=3, delay=0.3) + self.assertNotIn(call(a=1), something.method_1.mock_calls) + + something.method_1.wait_until_any_call(a=1) + something.method_1.assert_called_with(a=1) + self.assertNotIn(call(b=2), something.method_1.mock_calls) + self.assertNotIn(call(c=3), something.method_1.mock_calls) + + something.method_1.wait_until_any_call(c=3) + self.assertIn(call(b=2), something.method_1.mock_calls) + something.method_1.wait_until_any_call(b=2) + + def test_wait_until_any_call_no_argument_fails_when_called_with_arg(self): + waitable_mock = self._make_mock(timeout=0.01) + + with patch(f"{__name__}.Something", waitable_mock): + something = Something() + something.method_1(1) + + something.method_1.assert_called_with(1) + with self.assertRaises(AssertionError): + something.method_1.wait_until_any_call() + + something.method_1() + something.method_1.wait_until_any_call() + + def test_wait_until_any_call_global_default(self): + with patch.object(ThreadingMock, "DEFAULT_TIMEOUT"): + ThreadingMock.DEFAULT_TIMEOUT = 0.01 + m = self._make_mock() + with self.assertRaises(AssertionError): + m.wait_until_any_call() + with self.assertRaises(AssertionError): + m.wait_until_called() + + m() + m.wait_until_any_call() + assert ThreadingMock.DEFAULT_TIMEOUT != 0.01 + + def test_wait_until_any_call_change_global_and_override(self): + with patch.object(ThreadingMock, "DEFAULT_TIMEOUT"): + ThreadingMock.DEFAULT_TIMEOUT = 0.01 + + m1 = self._make_mock() + self.run_async(m1, delay=0.1) + with self.assertRaises(AssertionError): + m1.wait_until_called() + + m2 = self._make_mock(timeout=1) + self.run_async(m2, delay=0.1) + m2.wait_until_called() + + m3 = self._make_mock() + self.run_async(m3, delay=0.1) + m3.wait_until_called(timeout=1) + + m4 = self._make_mock() + self.run_async(m4, delay=0.1) + m4.wait_until_called(timeout=None) + + m5 = self._make_mock(timeout=None) + self.run_async(m5, delay=0.1) + m5.wait_until_called() + + assert ThreadingMock.DEFAULT_TIMEOUT != 0.01 + + def test_reset_mock_resets_wait(self): + m = self._make_mock(timeout=0.01) + + with self.assertRaises(AssertionError): + m.wait_until_called() + with self.assertRaises(AssertionError): + m.wait_until_any_call() + m() + m.wait_until_called() + m.wait_until_any_call() + m.assert_called_once() + + m.reset_mock() + + with self.assertRaises(AssertionError): + m.wait_until_called() + with self.assertRaises(AssertionError): + m.wait_until_any_call() + m() + m.wait_until_called() + m.wait_until_any_call() + m.assert_called_once() + + +if __name__ == "__main__": + unittest.main() From 0dc15d43989f7563b442b82e2807819b50b9d726 Mon Sep 17 00:00:00 2001 From: Mario Corchero Date: Tue, 4 Jul 2023 19:34:43 +0200 Subject: [PATCH 338/388] gh-61215: Rename `wait_until_any_call` to `wait_until_any_call_with` (#106414) mock: Rename `wait_until_any_call` to `wait_until_any_call_with` Rename the method to be more explicit that it expects the args and kwargs to wait for. Backports: 2dfc7fae787e65726f24bfe9efe05418b05ee8e2 Signed-off-by: Chris Withers --- mock/mock.py | 2 +- mock/tests/testthreadingmock.py | 50 ++++++++++++++++----------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/mock/mock.py b/mock/mock.py index 59793645..10a8403c 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -3122,7 +3122,7 @@ def wait_until_called(self, *, timeout=_timeout_unset): f" timeout({timeout}).") raise AssertionError(msg) - def wait_until_any_call(self, *args, **kwargs): + def wait_until_any_call_with(self, *args, **kwargs): """Wait until the mock object is called with given args. Waits for the timeout in seconds provided in the constructor. diff --git a/mock/tests/testthreadingmock.py b/mock/tests/testthreadingmock.py index 7a9072dd..70db52d8 100644 --- a/mock/tests/testthreadingmock.py +++ b/mock/tests/testthreadingmock.py @@ -87,7 +87,7 @@ def test_no_name_clash(self): waitable_mock.timeout = "mytimeout" waitable_mock("works") waitable_mock.wait_until_called() - waitable_mock.wait_until_any_call("works") + waitable_mock.wait_until_any_call_with("works") def test_wait_success(self): waitable_mock = self._make_mock(spec=Something) @@ -96,7 +96,7 @@ def test_wait_success(self): something = Something() self.run_async(something.method_1, delay=0.01) something.method_1.wait_until_called() - something.method_1.wait_until_any_call() + something.method_1.wait_until_any_call_with() something.method_1.assert_called() def test_wait_success_with_instance_timeout(self): @@ -106,7 +106,7 @@ def test_wait_success_with_instance_timeout(self): something = Something() self.run_async(something.method_1, delay=0.01) something.method_1.wait_until_called() - something.method_1.wait_until_any_call() + something.method_1.wait_until_any_call_with() something.method_1.assert_called() def test_wait_failed_with_instance_timeout(self): @@ -117,7 +117,7 @@ def test_wait_failed_with_instance_timeout(self): self.run_async(something.method_1, delay=0.5) self.assertRaises(AssertionError, waitable_mock.method_1.wait_until_called) self.assertRaises( - AssertionError, waitable_mock.method_1.wait_until_any_call + AssertionError, waitable_mock.method_1.wait_until_any_call_with ) def test_wait_success_with_timeout_override(self): @@ -137,7 +137,7 @@ def test_wait_failed_with_timeout_override(self): with self.assertRaises(AssertionError): something.method_1.wait_until_called(timeout=0.05) with self.assertRaises(AssertionError): - something.method_1.wait_until_any_call(timeout=0.05) + something.method_1.wait_until_any_call_with(timeout=0.05) def test_wait_success_called_before(self): waitable_mock = self._make_mock() @@ -146,7 +146,7 @@ def test_wait_success_called_before(self): something = Something() something.method_1() something.method_1.wait_until_called() - something.method_1.wait_until_any_call() + something.method_1.wait_until_any_call_with() something.method_1.assert_called() def test_wait_magic_method(self): @@ -158,7 +158,7 @@ def test_wait_magic_method(self): something.method_1.__str__.wait_until_called() something.method_1.__str__.assert_called() - def test_wait_until_any_call_positional(self): + def test_wait_until_any_call_with_positional(self): waitable_mock = self._make_mock(spec=Something) with patch(f"{__name__}.Something", waitable_mock): @@ -168,16 +168,16 @@ def test_wait_until_any_call_positional(self): self.run_async(something.method_1, 3, delay=0.3) self.assertNotIn(call(1), something.method_1.mock_calls) - something.method_1.wait_until_any_call(1) + something.method_1.wait_until_any_call_with(1) something.method_1.assert_called_with(1) self.assertNotIn(call(2), something.method_1.mock_calls) self.assertNotIn(call(3), something.method_1.mock_calls) - something.method_1.wait_until_any_call(3) + something.method_1.wait_until_any_call_with(3) self.assertIn(call(2), something.method_1.mock_calls) - something.method_1.wait_until_any_call(2) + something.method_1.wait_until_any_call_with(2) - def test_wait_until_any_call_keywords(self): + def test_wait_until_any_call_with_keywords(self): waitable_mock = self._make_mock(spec=Something) with patch(f"{__name__}.Something", waitable_mock): @@ -187,16 +187,16 @@ def test_wait_until_any_call_keywords(self): self.run_async(something.method_1, c=3, delay=0.3) self.assertNotIn(call(a=1), something.method_1.mock_calls) - something.method_1.wait_until_any_call(a=1) + something.method_1.wait_until_any_call_with(a=1) something.method_1.assert_called_with(a=1) self.assertNotIn(call(b=2), something.method_1.mock_calls) self.assertNotIn(call(c=3), something.method_1.mock_calls) - something.method_1.wait_until_any_call(c=3) + something.method_1.wait_until_any_call_with(c=3) self.assertIn(call(b=2), something.method_1.mock_calls) - something.method_1.wait_until_any_call(b=2) + something.method_1.wait_until_any_call_with(b=2) - def test_wait_until_any_call_no_argument_fails_when_called_with_arg(self): + def test_wait_until_any_call_with_no_argument_fails_when_called_with_arg(self): waitable_mock = self._make_mock(timeout=0.01) with patch(f"{__name__}.Something", waitable_mock): @@ -205,25 +205,25 @@ def test_wait_until_any_call_no_argument_fails_when_called_with_arg(self): something.method_1.assert_called_with(1) with self.assertRaises(AssertionError): - something.method_1.wait_until_any_call() + something.method_1.wait_until_any_call_with() something.method_1() - something.method_1.wait_until_any_call() + something.method_1.wait_until_any_call_with() - def test_wait_until_any_call_global_default(self): + def test_wait_until_any_call_with_global_default(self): with patch.object(ThreadingMock, "DEFAULT_TIMEOUT"): ThreadingMock.DEFAULT_TIMEOUT = 0.01 m = self._make_mock() with self.assertRaises(AssertionError): - m.wait_until_any_call() + m.wait_until_any_call_with() with self.assertRaises(AssertionError): m.wait_until_called() m() - m.wait_until_any_call() + m.wait_until_any_call_with() assert ThreadingMock.DEFAULT_TIMEOUT != 0.01 - def test_wait_until_any_call_change_global_and_override(self): + def test_wait_until_any_call_with_change_global_and_override(self): with patch.object(ThreadingMock, "DEFAULT_TIMEOUT"): ThreadingMock.DEFAULT_TIMEOUT = 0.01 @@ -256,10 +256,10 @@ def test_reset_mock_resets_wait(self): with self.assertRaises(AssertionError): m.wait_until_called() with self.assertRaises(AssertionError): - m.wait_until_any_call() + m.wait_until_any_call_with() m() m.wait_until_called() - m.wait_until_any_call() + m.wait_until_any_call_with() m.assert_called_once() m.reset_mock() @@ -267,10 +267,10 @@ def test_reset_mock_resets_wait(self): with self.assertRaises(AssertionError): m.wait_until_called() with self.assertRaises(AssertionError): - m.wait_until_any_call() + m.wait_until_any_call_with() m() m.wait_until_called() - m.wait_until_any_call() + m.wait_until_any_call_with() m.assert_called_once() From 9cbc589905dbb0f415ab3a995eee48f4906c0418 Mon Sep 17 00:00:00 2001 From: Mario Corchero Date: Thu, 6 Jul 2023 19:54:45 +0200 Subject: [PATCH 339/388] gh-106458: Mark `testthreadingmock.py` with `@requires_working_threading` (GH-106366) Mark `testthreadingmock.py` with `threading_helper.requires_working_threading`. Also add longer delays to reduce the change of a race conditions on the tests that validate short timeouts. Backports: 56353b10023ff12c7c8d6288ae4bf7bdcd5d4b6c Signed-off-by: Chris Withers --- lastsync.txt | 2 +- mock/tests/testthreadingmock.py | 16 +++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/lastsync.txt b/lastsync.txt index 2e06272f..9ef9ab7f 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -3f7c0810f6158a7ff37be432f8d7f9511427489f +2dfc7fae787e65726f24bfe9efe05418b05ee8e2 diff --git a/mock/tests/testthreadingmock.py b/mock/tests/testthreadingmock.py index 70db52d8..fe006904 100644 --- a/mock/tests/testthreadingmock.py +++ b/mock/tests/testthreadingmock.py @@ -133,11 +133,9 @@ def test_wait_failed_with_timeout_override(self): with patch(f"{__name__}.Something", waitable_mock): something = Something() - self.run_async(something.method_1, delay=0.1) + self.run_async(something.method_1, delay=0.5) with self.assertRaises(AssertionError): something.method_1.wait_until_called(timeout=0.05) - with self.assertRaises(AssertionError): - something.method_1.wait_until_any_call_with(timeout=0.05) def test_wait_success_called_before(self): waitable_mock = self._make_mock() @@ -163,10 +161,10 @@ def test_wait_until_any_call_with_positional(self): with patch(f"{__name__}.Something", waitable_mock): something = Something() - self.run_async(something.method_1, 1, delay=0.1) - self.run_async(something.method_1, 2, delay=0.2) - self.run_async(something.method_1, 3, delay=0.3) + self.run_async(something.method_1, 1, delay=0.2) self.assertNotIn(call(1), something.method_1.mock_calls) + self.run_async(something.method_1, 2, delay=0.5) + self.run_async(something.method_1, 3, delay=0.6) something.method_1.wait_until_any_call_with(1) something.method_1.assert_called_with(1) @@ -182,10 +180,10 @@ def test_wait_until_any_call_with_keywords(self): with patch(f"{__name__}.Something", waitable_mock): something = Something() - self.run_async(something.method_1, a=1, delay=0.1) - self.run_async(something.method_1, b=2, delay=0.2) - self.run_async(something.method_1, c=3, delay=0.3) + self.run_async(something.method_1, a=1, delay=0.2) self.assertNotIn(call(a=1), something.method_1.mock_calls) + self.run_async(something.method_1, b=2, delay=0.5) + self.run_async(something.method_1, c=3, delay=0.6) something.method_1.wait_until_any_call_with(a=1) something.method_1.assert_called_with(a=1) From 8d36bf4cc5f86cb7cc0b60d7b9f2c9c5e53f7f37 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Fri, 7 Jul 2023 23:42:40 +0300 Subject: [PATCH 340/388] gh-106300: Improve `assertRaises(Exception)` usages in tests (GH-106302) Backports: 6e6a4cd52332017b10c8d88fbbbfe015948093f4 Signed-off-by: Chris Withers --- mock/tests/testasync.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index 7fd680f4..f21b9fa8 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -469,9 +469,10 @@ async def addition(self, var): pass self.assertEqual(output, 10) async def test_add_side_effect_exception(self): + class CustomError(Exception): pass async def addition(var): pass - mock = AsyncMock(addition, side_effect=Exception('err')) - with self.assertRaises(Exception): + mock = AsyncMock(addition, side_effect=CustomError('side-effect')) + with self.assertRaisesRegex(CustomError, 'side-effect'): await mock(5) async def test_add_side_effect_coroutine(self): From acd5fc58e41cd866b4ceff262140bd3304fb127a Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 10 Jul 2023 09:40:19 +0100 Subject: [PATCH 341/388] Python 3.6+ compat --- mock/mock.py | 4 ++-- mock/tests/testthreadingmock.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mock/mock.py b/mock/mock.py index 10a8403c..4bd5b1e4 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -3063,7 +3063,7 @@ class ThreadingMixin(Base): DEFAULT_TIMEOUT = None - def _get_child_mock(self, /, **kw): + def _get_child_mock(self, **kw): if "timeout" in kw: kw["timeout"] = kw.pop("timeout") elif isinstance(kw.get("parent"), ThreadingMixin): @@ -3081,7 +3081,7 @@ def __init__(self, *args, timeout=_timeout_unset, **kwargs): self.__dict__["_mock_calls_events_lock"] = threading.Lock() self.__dict__["_mock_wait_timeout"] = timeout - def reset_mock(self, /, *args, **kwargs): + def reset_mock(self, *args, **kwargs): """ See :func:`.Mock.reset_mock()` """ diff --git a/mock/tests/testthreadingmock.py b/mock/tests/testthreadingmock.py index fe006904..3637d818 100644 --- a/mock/tests/testthreadingmock.py +++ b/mock/tests/testthreadingmock.py @@ -14,7 +14,7 @@ def method_2(self): class TestThreadingMock(unittest.TestCase): - def _call_after_delay(self, func, /, *args, **kwargs): + def _call_after_delay(self, func, *args, **kwargs): time.sleep(kwargs.pop("delay")) func(*args, **kwargs) @@ -24,7 +24,7 @@ def setUp(self): def tearDown(self): self._executor.shutdown() - def run_async(self, func, /, *args, delay=0, **kwargs): + def run_async(self, func, *args, delay=0, **kwargs): self._executor.submit( self._call_after_delay, func, *args, **kwargs, delay=delay ) From f0cc38503e2d91341ca4e263d5acf934b7c0d130 Mon Sep 17 00:00:00 2001 From: Mario Corchero Date: Mon, 10 Jul 2023 15:35:54 +0200 Subject: [PATCH 342/388] GH-61215: threadingmock: Remove unused branch for `timeout` (#106591) threadingmock: Remove unused branch for `timeout` This is no longer needed as the mock does not hold a "timeout" parameter, the timeout is stored in `_mock_wait_timeout`. Backports: 3e23fa71f43fb225ca29a931644d1100e2f4d6b8 Signed-off-by: Chris Withers --- mock/mock.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mock/mock.py b/mock/mock.py index 4bd5b1e4..fba26bd2 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -3064,9 +3064,7 @@ class ThreadingMixin(Base): DEFAULT_TIMEOUT = None def _get_child_mock(self, **kw): - if "timeout" in kw: - kw["timeout"] = kw.pop("timeout") - elif isinstance(kw.get("parent"), ThreadingMixin): + if isinstance(kw.get("parent"), ThreadingMixin): kw["timeout"] = kw["parent"]._mock_wait_timeout elif isinstance(kw.get("_new_parent"), ThreadingMixin): kw["timeout"] = kw["_new_parent"]._mock_wait_timeout From 73343195e09fd20b9a39187a71c138fa13e02f9d Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 11 Jul 2023 09:52:12 +0100 Subject: [PATCH 343/388] Remove unused branches from mock module (#106617) * lambda has a name of __none__, but no async lambda so this branch is not needed * _get_signature_object only returns None for bound builtins. There are no async builtins so this branch isn't needed * Exclude a couple of methods from coverage checking in the downstream rolling backport of mock Backports: e6379f72cbc60f6b3c5676f9e225d4f145d5693f Signed-off-by: Chris Withers --- mock/mock.py | 7 +------ mock/tests/testthreadingmock.py | 4 ++-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/mock/mock.py b/mock/mock.py index fba26bd2..448b7ef1 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -214,17 +214,12 @@ def _set_async_signature(mock, original, instance=False, is_async_mock=False): # signature as the original. skipfirst = isinstance(original, type) - result = _get_signature_object(original, instance, skipfirst) - if result is None: - return mock - func, sig = result + func, sig = _get_signature_object(original, instance, skipfirst) def checksig(*args, **kwargs): sig.bind(*args, **kwargs) _copy_func_details(func, checksig) name = original.__name__ - if not name.isidentifier(): - name = 'funcopy' context = {'_checksig_': checksig, 'mock': mock} src = """async def %s(*args, **kwargs): _checksig_(*args, **kwargs) diff --git a/mock/tests/testthreadingmock.py b/mock/tests/testthreadingmock.py index 3637d818..6288e2d4 100644 --- a/mock/tests/testthreadingmock.py +++ b/mock/tests/testthreadingmock.py @@ -7,10 +7,10 @@ class Something: def method_1(self): - pass + pass # pragma: no cover def method_2(self): - pass + pass # pragma: no cover class TestThreadingMock(unittest.TestCase): From e53a01c315095bac1101fb3545c61382dc0d23fd Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 10 Jul 2023 09:35:51 +0100 Subject: [PATCH 344/388] latest sync point --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index 9ef9ab7f..06e5d6af 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -2dfc7fae787e65726f24bfe9efe05418b05ee8e2 +e6379f72cbc60f6b3c5676f9e225d4f145d5693f From d344fa2794b3b1ae7e4a4dbf265fb040d6f41d1f Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Tue, 11 Jul 2023 14:32:24 +0100 Subject: [PATCH 345/388] Preparing for 5.1.0 release. --- CHANGELOG.rst | 17 +++++++++++++++++ NEWS.d/2019-09-13-13-28-10.bpo-17013.NWcgE3.rst | 3 --- NEWS.d/2020-09-16-16-53-06.bpo-41768.8_fWkC.rst | 2 -- NEWS.d/2021-06-24-20-45-03.bpo-44185.ZHb8yJ.rst | 3 --- ...022-07-18-14-20-56.gh-issue-94924.X0buz2.rst | 1 - mock/__init__.py | 2 +- 6 files changed, 18 insertions(+), 10 deletions(-) delete mode 100644 NEWS.d/2019-09-13-13-28-10.bpo-17013.NWcgE3.rst delete mode 100644 NEWS.d/2020-09-16-16-53-06.bpo-41768.8_fWkC.rst delete mode 100644 NEWS.d/2021-06-24-20-45-03.bpo-44185.ZHb8yJ.rst delete mode 100644 NEWS.d/2022-07-18-14-20-56.gh-issue-94924.X0buz2.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3fa5887e..6e8b6973 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,20 @@ +5.1.0 +----- + +- bpo-44185: :func:`unittest.mock.mock_open` will call the :func:`close` + method of the file handle mock when it is exiting from the context + manager. Patch by Samet Yaslan. + +- gh-94924: :func:`unittest.mock.create_autospec` now properly returns + coroutine functions compatible with :func:`inspect.iscoroutinefunction` + +- bpo-17013: Add ``ThreadingMock`` to :mod:`unittest.mock` that can be used + to create Mock objects that can wait until they are called. Patch by + Karthikeyan Singaravelan and Mario Corchero. + +- bpo-41768: :mod:`unittest.mock` speccing no longer calls class properties. + Patch by Melanie Witt. + 5.0.2 ----- diff --git a/NEWS.d/2019-09-13-13-28-10.bpo-17013.NWcgE3.rst b/NEWS.d/2019-09-13-13-28-10.bpo-17013.NWcgE3.rst deleted file mode 100644 index ac746c45..00000000 --- a/NEWS.d/2019-09-13-13-28-10.bpo-17013.NWcgE3.rst +++ /dev/null @@ -1,3 +0,0 @@ -Add ``ThreadingMock`` to :mod:`unittest.mock` that can be used to create -Mock objects that can wait until they are called. Patch by Karthikeyan -Singaravelan and Mario Corchero. diff --git a/NEWS.d/2020-09-16-16-53-06.bpo-41768.8_fWkC.rst b/NEWS.d/2020-09-16-16-53-06.bpo-41768.8_fWkC.rst deleted file mode 100644 index bfd3a294..00000000 --- a/NEWS.d/2020-09-16-16-53-06.bpo-41768.8_fWkC.rst +++ /dev/null @@ -1,2 +0,0 @@ -:mod:`unittest.mock` speccing no longer calls class properties. -Patch by Melanie Witt. diff --git a/NEWS.d/2021-06-24-20-45-03.bpo-44185.ZHb8yJ.rst b/NEWS.d/2021-06-24-20-45-03.bpo-44185.ZHb8yJ.rst deleted file mode 100644 index 056ab8d9..00000000 --- a/NEWS.d/2021-06-24-20-45-03.bpo-44185.ZHb8yJ.rst +++ /dev/null @@ -1,3 +0,0 @@ -:func:`unittest.mock.mock_open` will call the :func:`close` method of the file -handle mock when it is exiting from the context manager. -Patch by Samet Yaslan. diff --git a/NEWS.d/2022-07-18-14-20-56.gh-issue-94924.X0buz2.rst b/NEWS.d/2022-07-18-14-20-56.gh-issue-94924.X0buz2.rst deleted file mode 100644 index 7882f224..00000000 --- a/NEWS.d/2022-07-18-14-20-56.gh-issue-94924.X0buz2.rst +++ /dev/null @@ -1 +0,0 @@ -:func:`unittest.mock.create_autospec` now properly returns coroutine functions compatible with :func:`inspect.iscoroutinefunction` diff --git a/mock/__init__.py b/mock/__init__.py index 85abb93e..9c4e2d01 100644 --- a/mock/__init__.py +++ b/mock/__init__.py @@ -7,7 +7,7 @@ import mock.mock as _mock from mock.mock import * -__version__ = '5.0.2' +__version__ = '5.1.0' version_info = tuple(int(p) for p in re.match(r'(\d+).(\d+).(\d+)', __version__).groups()) From 610303fdf8212932ede0c3d2fc904ddd9dc5ee05 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 17 Apr 2024 07:55:28 +0100 Subject: [PATCH 346/388] Newer RTD config --- .readthedocs.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 7687b8a8..4e72576a 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,8 +1,15 @@ version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3" + python: - version: 3.7 install: - method: pip path: . extra_requirements: - docs +sphinx: + fail_on_warning: true From 5662ae289fc8558b74f63284d002264756797ff7 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 17 Apr 2024 07:55:38 +0100 Subject: [PATCH 347/388] Newer Circle CI orb --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3bd8814a..df39572d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,7 +1,7 @@ version: 2.1 orbs: - python: cjw296/python-ci@3.4 + python: cjw296/python-ci@4 jobs: check-package: From 5e4d3fd95b2764e9f97069c23d093a4770940822 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Wed, 17 Apr 2024 07:55:47 +0100 Subject: [PATCH 348/388] Test on 3.12 --- .circleci/config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index df39572d..b3b1017b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,6 +36,7 @@ common: &common - cimg/python:3.9 - cimg/python:3.10 - cimg/python:3.11 + - cimg/python:3.12 - python/pip-run-tests: python: pypy3 @@ -70,7 +71,7 @@ common: &common parameters: image: - cimg/python:3.6 - - cimg/python:3.11 + - cimg/python:3.12 requires: - package From facd817882b7aa8c43cf4a6edffff2bec26a329f Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sat, 1 Mar 2025 10:39:59 +0000 Subject: [PATCH 349/388] Use latest orb version --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b3b1017b..43a18ea7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,7 +1,7 @@ version: 2.1 orbs: - python: cjw296/python-ci@4 + python: cjw296/python-ci@5 jobs: check-package: From 686d9a650584f0c08eb3fbf1ddcdf6ae38876f0b Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sat, 1 Mar 2025 10:40:11 +0000 Subject: [PATCH 350/388] Test against Python 3.13 --- .circleci/config.yml | 3 ++- setup.cfg | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 43a18ea7..6e2dad74 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -37,6 +37,7 @@ common: &common - cimg/python:3.10 - cimg/python:3.11 - cimg/python:3.12 + - cimg/python:3.13 - python/pip-run-tests: python: pypy3 @@ -71,7 +72,7 @@ common: &common parameters: image: - cimg/python:3.6 - - cimg/python:3.12 + - cimg/python:3.13 requires: - package diff --git a/setup.cfg b/setup.cfg index dfd0fa04..e702275a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -18,6 +18,8 @@ classifiers = Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 + Programming Language :: Python :: 3.13 Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: PyPy Topic :: Software Development :: Libraries From 446a221600923299e53aa54c4e029dd959594dc9 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sat, 1 Mar 2025 10:42:49 +0000 Subject: [PATCH 351/388] Add new required Read The Docs key --- .readthedocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.readthedocs.yml b/.readthedocs.yml index 4e72576a..63c8ae61 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -13,3 +13,4 @@ python: - docs sphinx: fail_on_warning: true + configuration: docs/conf.py From 32c45c81831bac9f12906cf3d37c068950c25aa7 Mon Sep 17 00:00:00 2001 From: Mario Corchero Date: Mon, 17 Jul 2023 20:57:40 +0200 Subject: [PATCH 352/388] gh-61215: threadingmock: Improve test suite to avoid race conditions (#106822) threadingmock: Improve test suite to avoid race conditions Simplify tests and split them into multiple tests to prevent assertions from triggering race conditions. Additionally, we rely on calling the mocks without delay to validate the functionality of matching calls. Backports: 7e96370a946a2ca0f2f25af4ce5b3b59f020721b Signed-off-by: Chris Withers --- mock/tests/testthreadingmock.py | 195 ++++++++++---------------------- 1 file changed, 58 insertions(+), 137 deletions(-) diff --git a/mock/tests/testthreadingmock.py b/mock/tests/testthreadingmock.py index 6288e2d4..3706f06a 100644 --- a/mock/tests/testthreadingmock.py +++ b/mock/tests/testthreadingmock.py @@ -4,6 +4,8 @@ from mock import patch, ThreadingMock, call +VERY_SHORT_TIMEOUT = 0.1 + class Something: def method_1(self): @@ -89,167 +91,86 @@ def test_no_name_clash(self): waitable_mock.wait_until_called() waitable_mock.wait_until_any_call_with("works") - def test_wait_success(self): + def test_patch(self): waitable_mock = self._make_mock(spec=Something) - with patch(f"{__name__}.Something", waitable_mock): - something = Something() - self.run_async(something.method_1, delay=0.01) - something.method_1.wait_until_called() - something.method_1.wait_until_any_call_with() - something.method_1.assert_called() - - def test_wait_success_with_instance_timeout(self): - waitable_mock = self._make_mock(timeout=1) - - with patch(f"{__name__}.Something", waitable_mock): - something = Something() - self.run_async(something.method_1, delay=0.01) - something.method_1.wait_until_called() - something.method_1.wait_until_any_call_with() - something.method_1.assert_called() - - def test_wait_failed_with_instance_timeout(self): - waitable_mock = self._make_mock(timeout=0.01) - - with patch(f"{__name__}.Something", waitable_mock): - something = Something() - self.run_async(something.method_1, delay=0.5) - self.assertRaises(AssertionError, waitable_mock.method_1.wait_until_called) - self.assertRaises( - AssertionError, waitable_mock.method_1.wait_until_any_call_with - ) - - def test_wait_success_with_timeout_override(self): - waitable_mock = self._make_mock(timeout=0.01) - - with patch(f"{__name__}.Something", waitable_mock): - something = Something() - self.run_async(something.method_1, delay=0.05) - something.method_1.wait_until_called(timeout=1) - - def test_wait_failed_with_timeout_override(self): - waitable_mock = self._make_mock(timeout=1) - - with patch(f"{__name__}.Something", waitable_mock): - something = Something() - self.run_async(something.method_1, delay=0.5) - with self.assertRaises(AssertionError): - something.method_1.wait_until_called(timeout=0.05) - - def test_wait_success_called_before(self): - waitable_mock = self._make_mock() - with patch(f"{__name__}.Something", waitable_mock): something = Something() something.method_1() something.method_1.wait_until_called() - something.method_1.wait_until_any_call_with() - something.method_1.assert_called() - - def test_wait_magic_method(self): - waitable_mock = self._make_mock() - with patch(f"{__name__}.Something", waitable_mock): - something = Something() - self.run_async(something.method_1.__str__, delay=0.01) - something.method_1.__str__.wait_until_called() - something.method_1.__str__.assert_called() - - def test_wait_until_any_call_with_positional(self): + def test_wait_already_called_success(self): waitable_mock = self._make_mock(spec=Something) + waitable_mock.method_1() + waitable_mock.method_1.wait_until_called() + waitable_mock.method_1.wait_until_any_call_with() + waitable_mock.method_1.assert_called() - with patch(f"{__name__}.Something", waitable_mock): - something = Something() - self.run_async(something.method_1, 1, delay=0.2) - self.assertNotIn(call(1), something.method_1.mock_calls) - self.run_async(something.method_1, 2, delay=0.5) - self.run_async(something.method_1, 3, delay=0.6) - - something.method_1.wait_until_any_call_with(1) - something.method_1.assert_called_with(1) - self.assertNotIn(call(2), something.method_1.mock_calls) - self.assertNotIn(call(3), something.method_1.mock_calls) - - something.method_1.wait_until_any_call_with(3) - self.assertIn(call(2), something.method_1.mock_calls) - something.method_1.wait_until_any_call_with(2) - - def test_wait_until_any_call_with_keywords(self): + def test_wait_until_called_success(self): waitable_mock = self._make_mock(spec=Something) + self.run_async(waitable_mock.method_1, delay=VERY_SHORT_TIMEOUT) + waitable_mock.method_1.wait_until_called() - with patch(f"{__name__}.Something", waitable_mock): - something = Something() - self.run_async(something.method_1, a=1, delay=0.2) - self.assertNotIn(call(a=1), something.method_1.mock_calls) - self.run_async(something.method_1, b=2, delay=0.5) - self.run_async(something.method_1, c=3, delay=0.6) - - something.method_1.wait_until_any_call_with(a=1) - something.method_1.assert_called_with(a=1) - self.assertNotIn(call(b=2), something.method_1.mock_calls) - self.assertNotIn(call(c=3), something.method_1.mock_calls) - - something.method_1.wait_until_any_call_with(c=3) - self.assertIn(call(b=2), something.method_1.mock_calls) - something.method_1.wait_until_any_call_with(b=2) - - def test_wait_until_any_call_with_no_argument_fails_when_called_with_arg(self): - waitable_mock = self._make_mock(timeout=0.01) - - with patch(f"{__name__}.Something", waitable_mock): - something = Something() - something.method_1(1) - - something.method_1.assert_called_with(1) - with self.assertRaises(AssertionError): - something.method_1.wait_until_any_call_with() + def test_wait_until_called_method_timeout(self): + waitable_mock = self._make_mock(spec=Something) + with self.assertRaises(AssertionError): + waitable_mock.method_1.wait_until_called(timeout=VERY_SHORT_TIMEOUT) - something.method_1() - something.method_1.wait_until_any_call_with() + def test_wait_until_called_instance_timeout(self): + waitable_mock = self._make_mock(spec=Something, timeout=VERY_SHORT_TIMEOUT) + with self.assertRaises(AssertionError): + waitable_mock.method_1.wait_until_called() - def test_wait_until_any_call_with_global_default(self): + def test_wait_until_called_global_timeout(self): with patch.object(ThreadingMock, "DEFAULT_TIMEOUT"): - ThreadingMock.DEFAULT_TIMEOUT = 0.01 - m = self._make_mock() + ThreadingMock.DEFAULT_TIMEOUT = VERY_SHORT_TIMEOUT + waitable_mock = self._make_mock(spec=Something) with self.assertRaises(AssertionError): - m.wait_until_any_call_with() - with self.assertRaises(AssertionError): - m.wait_until_called() + waitable_mock.method_1.wait_until_called() - m() - m.wait_until_any_call_with() - assert ThreadingMock.DEFAULT_TIMEOUT != 0.01 + def test_wait_until_any_call_with_success(self): + waitable_mock = self._make_mock() + self.run_async(waitable_mock, delay=VERY_SHORT_TIMEOUT) + waitable_mock.wait_until_any_call_with() - def test_wait_until_any_call_with_change_global_and_override(self): - with patch.object(ThreadingMock, "DEFAULT_TIMEOUT"): - ThreadingMock.DEFAULT_TIMEOUT = 0.01 + def test_wait_until_any_call_with_instance_timeout(self): + waitable_mock = self._make_mock(timeout=VERY_SHORT_TIMEOUT) + with self.assertRaises(AssertionError): + waitable_mock.wait_until_any_call_with() - m1 = self._make_mock() - self.run_async(m1, delay=0.1) + def test_wait_until_any_call_global_timeout(self): + with patch.object(ThreadingMock, "DEFAULT_TIMEOUT"): + ThreadingMock.DEFAULT_TIMEOUT = VERY_SHORT_TIMEOUT + waitable_mock = self._make_mock() with self.assertRaises(AssertionError): - m1.wait_until_called() + waitable_mock.wait_until_any_call_with() - m2 = self._make_mock(timeout=1) - self.run_async(m2, delay=0.1) - m2.wait_until_called() - - m3 = self._make_mock() - self.run_async(m3, delay=0.1) - m3.wait_until_called(timeout=1) - - m4 = self._make_mock() - self.run_async(m4, delay=0.1) - m4.wait_until_called(timeout=None) + def test_wait_until_any_call_positional(self): + waitable_mock = self._make_mock(timeout=VERY_SHORT_TIMEOUT) + waitable_mock.method_1(1, 2, 3) + waitable_mock.method_1.wait_until_any_call_with(1, 2, 3) + with self.assertRaises(AssertionError): + waitable_mock.method_1.wait_until_any_call_with(2, 3, 1) + with self.assertRaises(AssertionError): + waitable_mock.method_1.wait_until_any_call_with() - m5 = self._make_mock(timeout=None) - self.run_async(m5, delay=0.1) - m5.wait_until_called() + def test_wait_until_any_call_kw(self): + waitable_mock = self._make_mock(timeout=VERY_SHORT_TIMEOUT) + waitable_mock.method_1(a=1, b=2) + waitable_mock.method_1.wait_until_any_call_with(a=1, b=2) + with self.assertRaises(AssertionError): + waitable_mock.method_1.wait_until_any_call_with(a=2, b=1) + with self.assertRaises(AssertionError): + waitable_mock.method_1.wait_until_any_call_with() - assert ThreadingMock.DEFAULT_TIMEOUT != 0.01 + def test_magic_methods_success(self): + waitable_mock = self._make_mock() + str(waitable_mock) + waitable_mock.__str__.wait_until_called() + waitable_mock.__str__.assert_called() def test_reset_mock_resets_wait(self): - m = self._make_mock(timeout=0.01) + m = self._make_mock(timeout=VERY_SHORT_TIMEOUT) with self.assertRaises(AssertionError): m.wait_until_called() From cbb6ac2642f60b84728e280c31c333a8848949d4 Mon Sep 17 00:00:00 2001 From: Sangyun_LEE Date: Mon, 4 Sep 2023 06:19:49 +0900 Subject: [PATCH 353/388] Update Lib/test/test_unittest/testmock/testmock.py: fix typo RuntimError to RuntimeError (#108847) Backports: 0c369d6cb8c9a73725f5794c84bedf93e46fd27d Signed-off-by: Chris Withers --- mock/tests/testmock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 796e20e4..4ff01570 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -2273,7 +2273,7 @@ def test_misspelled_arguments(self): class Foo(): one = 'one' # patch, patch.object and create_autospec need to check for misspelled - # arguments explicitly and throw a RuntimError if found. + # arguments explicitly and throw a RuntimeError if found. with self.assertRaises(RuntimeError): with patch(f'{__name__}.Something.meth', autospect=True): pass with self.assertRaises(RuntimeError): From df91736bab55de4dee5f7d5b8adc4e45d1840815 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Sun, 24 Sep 2023 15:07:23 +0100 Subject: [PATCH 354/388] gh-109653: Remove unused imports in the `Lib/` directory (#109803) Backports: 19601efa364fe3c294d8010effe11e025cbc230e Signed-off-by: Chris Withers --- mock/tests/testthreadingmock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock/tests/testthreadingmock.py b/mock/tests/testthreadingmock.py index 3706f06a..d65834d4 100644 --- a/mock/tests/testthreadingmock.py +++ b/mock/tests/testthreadingmock.py @@ -2,7 +2,7 @@ import unittest import concurrent.futures -from mock import patch, ThreadingMock, call +from mock import patch, ThreadingMock VERY_SHORT_TIMEOUT = 0.1 From 00bd26f4a2b4396962f3abbb78333da031d27bda Mon Sep 17 00:00:00 2001 From: James Date: Wed, 18 Oct 2023 03:36:16 -0400 Subject: [PATCH 355/388] gh-111019: Align expected and actual titles in test output (#111020) Align expected and actual titles in output from assert_has_calls/assert_called_with for greater readability Backports: 77dbd956090aac66e264d9d640f6adb6b0930b87 Signed-off-by: Chris Withers --- mock/mock.py | 6 +++--- mock/tests/testmock.py | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/mock/mock.py b/mock/mock.py index 448b7ef1..861c9cc6 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -859,7 +859,7 @@ def _format_mock_call_signature(self, args, kwargs): def _format_mock_failure_message(self, args, kwargs, action='call'): - message = 'expected %s not found.\nExpected: %s\nActual: %s' + message = 'expected %s not found.\nExpected: %s\n Actual: %s' expected_string = self._format_mock_call_signature(args, kwargs) call_args = self.call_args actual_string = self._format_mock_call_signature(*call_args) @@ -966,7 +966,7 @@ def assert_called_with(_mock_self, *args, **kwargs): if self.call_args is None: expected = self._format_mock_call_signature(args, kwargs) actual = 'not called.' - error_message = ('expected call not found.\nExpected: %s\nActual: %s' + error_message = ('expected call not found.\nExpected: %s\n Actual: %s' % (expected, actual)) raise AssertionError(error_message) @@ -1018,7 +1018,7 @@ def assert_has_calls(self, calls, any_order=False): raise AssertionError( f'{problem}\n' f'Expected: {_CallList(calls)}' - f'{self._calls_repr(prefix="Actual").rstrip(".")}' + f'{self._calls_repr(prefix=" Actual").rstrip(".")}' ) from cause return diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 4ff01570..e129e692 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -1059,7 +1059,7 @@ def test_assert_called_with_failure_message(self): actual = 'not called.' expected = "mock(1, '2', 3, bar='foo')" - message = 'expected call not found.\nExpected: %s\nActual: %s' + message = 'expected call not found.\nExpected: %s\n Actual: %s' self.assertRaisesWithMsg( AssertionError, message % (expected, actual), mock.assert_called_with, 1, '2', 3, bar='foo' @@ -1074,7 +1074,7 @@ def test_assert_called_with_failure_message(self): for meth in asserters: actual = "foo(1, '2', 3, foo='foo')" expected = "foo(1, '2', 3, bar='foo')" - message = 'expected call not found.\nExpected: %s\nActual: %s' + message = 'expected call not found.\nExpected: %s\n Actual: %s' self.assertRaisesWithMsg( AssertionError, message % (expected, actual), meth, 1, '2', 3, bar='foo' @@ -1084,7 +1084,7 @@ def test_assert_called_with_failure_message(self): for meth in asserters: actual = "foo(1, '2', 3, foo='foo')" expected = "foo(bar='foo')" - message = 'expected call not found.\nExpected: %s\nActual: %s' + message = 'expected call not found.\nExpected: %s\n Actual: %s' self.assertRaisesWithMsg( AssertionError, message % (expected, actual), meth, bar='foo' @@ -1094,7 +1094,7 @@ def test_assert_called_with_failure_message(self): for meth in asserters: actual = "foo(1, '2', 3, foo='foo')" expected = "foo(1, 2, 3)" - message = 'expected call not found.\nExpected: %s\nActual: %s' + message = 'expected call not found.\nExpected: %s\n Actual: %s' self.assertRaisesWithMsg( AssertionError, message % (expected, actual), meth, 1, 2, 3 @@ -1104,7 +1104,7 @@ def test_assert_called_with_failure_message(self): for meth in asserters: actual = "foo(1, '2', 3, foo='foo')" expected = "foo()" - message = 'expected call not found.\nExpected: %s\nActual: %s' + message = 'expected call not found.\nExpected: %s\n Actual: %s' self.assertRaisesWithMsg( AssertionError, message % (expected, actual), meth ) @@ -1553,7 +1553,7 @@ def f(x=None): pass '^{}$'.format( re.escape('Calls not found.\n' 'Expected: [call()]\n' - 'Actual: [call(1)]'))) as cm: + ' Actual: [call(1)]'))) as cm: mock.assert_has_calls([call()]) self.assertIsNone(cm.exception.__cause__) @@ -1565,7 +1565,7 @@ 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)]').replace( + ' Actual: [call(1)]').replace( "arguments\\'", "arguments\\',?" ))) as cm: mock.assert_has_calls([call(), call(1, 2)]) From f2122976fc9142dd30c1ac5985c10e7c956e624c Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 24 Dec 2023 13:38:56 +0200 Subject: [PATCH 356/388] gh-113407: Fix import of unittest.mock when CPython is built without docstrings (GH-113408) Backports: 0c574540e07792cef5487aef61ab38bfe404060f Signed-off-by: Chris Withers --- ...023-12-22-20-49-52.gh-issue-113407.C_O13_.rst | 1 + mock/mock.py | 16 +++++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 NEWS.d/2023-12-22-20-49-52.gh-issue-113407.C_O13_.rst diff --git a/NEWS.d/2023-12-22-20-49-52.gh-issue-113407.C_O13_.rst b/NEWS.d/2023-12-22-20-49-52.gh-issue-113407.C_O13_.rst new file mode 100644 index 00000000..da00977f --- /dev/null +++ b/NEWS.d/2023-12-22-20-49-52.gh-issue-113407.C_O13_.rst @@ -0,0 +1 @@ +Fix import of :mod:`unittest.mock` when CPython is built without docstrings. diff --git a/mock/mock.py b/mock/mock.py index 861c9cc6..b4f6d1c5 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2269,8 +2269,11 @@ def __get__(self, obj, _type=None): return self.create_mock() -_CODE_ATTRS = dir(CodeType) -_CODE_SIG = inspect.signature(partial(CodeType.__init__, None)) +try: + _CODE_SIG = inspect.signature(partial(CodeType.__init__, None)) + _CODE_ATTRS = dir(CodeType) +except ValueError: + _CODE_SIG = None class AsyncMockMixin(Base): @@ -2290,9 +2293,12 @@ def __init__(self, *args, **kwargs): self.__dict__['_mock_await_count'] = 0 self.__dict__['_mock_await_args'] = None self.__dict__['_mock_await_args_list'] = _CallList() - code_mock = NonCallableMock(spec_set=_CODE_ATTRS) - code_mock.__dict__["_spec_class"] = CodeType - code_mock.__dict__["_spec_signature"] = _CODE_SIG + if _CODE_SIG: + code_mock = NonCallableMock(spec_set=_CODE_ATTRS) + code_mock.__dict__["_spec_class"] = CodeType + code_mock.__dict__["_spec_signature"] = _CODE_SIG + else: + code_mock = NonCallableMock(spec_set=CodeType) code_mock.co_flags = ( inspect.CO_COROUTINE + inspect.CO_VARARGS From cfc2efb06b04008adecb0e26b375f44cd5079421 Mon Sep 17 00:00:00 2001 From: wookie184 Date: Thu, 4 Jan 2024 19:11:34 +0000 Subject: [PATCH 357/388] gh-113569: Display calls in Mock.assert_has_calls failure when empty (GH-113573) Backports: 1600d78e2d090319930c6538b496ffcca120a696 Signed-off-by: Chris Withers --- ...-12-29-17-57-45.gh-issue-113569.qcRCEI.rst | 2 + mock/mock.py | 8 ++-- mock/tests/testmock.py | 38 +++++++++++-------- 3 files changed, 28 insertions(+), 20 deletions(-) create mode 100644 NEWS.d/2023-12-29-17-57-45.gh-issue-113569.qcRCEI.rst diff --git a/NEWS.d/2023-12-29-17-57-45.gh-issue-113569.qcRCEI.rst b/NEWS.d/2023-12-29-17-57-45.gh-issue-113569.qcRCEI.rst new file mode 100644 index 00000000..9b63fc94 --- /dev/null +++ b/NEWS.d/2023-12-29-17-57-45.gh-issue-113569.qcRCEI.rst @@ -0,0 +1,2 @@ +Indicate if there were no actual calls in unittest +:meth:`~unittest.mock.Mock.assert_has_calls` failure. diff --git a/mock/mock.py b/mock/mock.py index b4f6d1c5..f4ccbb0a 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1017,8 +1017,8 @@ def assert_has_calls(self, calls, any_order=False): for e in expected]) raise AssertionError( f'{problem}\n' - f'Expected: {_CallList(calls)}' - f'{self._calls_repr(prefix=" Actual").rstrip(".")}' + f'Expected: {_CallList(calls)}\n' + f' Actual: {safe_repr(self.mock_calls)}' ) from cause return @@ -1092,7 +1092,7 @@ def _get_child_mock(self, **kw): return klass(**kw) - def _calls_repr(self, prefix="Calls"): + def _calls_repr(self): """Renders self.mock_calls as a string. Example: "\nCalls: [call(1), call(2)]." @@ -1102,7 +1102,7 @@ def _calls_repr(self, prefix="Calls"): """ if not self.mock_calls: return "" - return f"\n{prefix}: {safe_repr(self.mock_calls)}." + return f"\nCalls: {safe_repr(self.mock_calls)}." try: diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index e129e692..0261d55a 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -1548,27 +1548,33 @@ def f(x=None): pass mock = Mock(spec=f) mock(1) - with self.assertRaisesRegex( - AssertionError, - '^{}$'.format( - re.escape('Calls not found.\n' - 'Expected: [call()]\n' - ' Actual: [call(1)]'))) as cm: + with self.assertRaises(AssertionError) as cm: mock.assert_has_calls([call()]) + self.assertEqual(str(cm.exception), + 'Calls not found.\n' + 'Expected: [call()]\n' + ' Actual: [call(1)]' + ) self.assertIsNone(cm.exception.__cause__) + uncalled_mock = Mock() + with self.assertRaises(AssertionError) as cm: + uncalled_mock.assert_has_calls([call()]) + self.assertEqual(str(cm.exception), + 'Calls not found.\n' + 'Expected: [call()]\n' + ' Actual: []' + ) + self.assertIsNone(cm.exception.__cause__) - with self.assertRaisesRegex( - AssertionError, - '^{}$'.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)]').replace( - "arguments\\'", "arguments\\',?" - ))) as cm: + with self.assertRaises(AssertionError) as cm: mock.assert_has_calls([call(), call(1, 2)]) + self.assertEqual(str(cm.exception), + 'Error processing expected calls.\n' + "Errors: [None, TypeError('too many positional arguments')]\n" + 'Expected: [call(), call(1, 2)]\n' + ' Actual: [call(1)]' + ) self.assertIsInstance(cm.exception.__cause__, TypeError) def test_assert_any_call(self): From 4953c808d867716409a3162f5b1facbc8d41341e Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sat, 1 Mar 2025 18:11:00 +0000 Subject: [PATCH 358/388] Python 3.6 error message assertion --- mock/tests/testmock.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 0261d55a..fffdf7c9 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -1569,12 +1569,21 @@ def f(x=None): pass with self.assertRaises(AssertionError) as cm: mock.assert_has_calls([call(), call(1, 2)]) - self.assertEqual(str(cm.exception), + if sys.version_info[:2] > (3, 6): + message = ( 'Error processing expected calls.\n' "Errors: [None, TypeError('too many positional arguments')]\n" 'Expected: [call(), call(1, 2)]\n' ' Actual: [call(1)]' - ) + ) + else: + message = ( + 'Error processing expected calls.\n' + "Errors: [None, TypeError('too many positional arguments',)]\n" + 'Expected: [call(), call(1, 2)]\n' + ' Actual: [call(1)]' + ) + self.assertEqual(str(cm.exception), message) self.assertIsInstance(cm.exception.__cause__, TypeError) def test_assert_any_call(self): From 0e62c9d5b05d8f59b65bf8dfec93ddaa320ac6c3 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Sun, 11 Feb 2024 11:51:25 +0300 Subject: [PATCH 359/388] gh-115274: Fix direct invocation of `testmock/testpatch.py` (#115275) Backports: f8e9c57067e32baab4ed2fd824b892c52ecb7225 Signed-off-by: Chris Withers --- mock/tests/testpatch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index e15e9a22..eb9b2779 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -1912,7 +1912,7 @@ def foo(x=0): with patch.object(foo, '__module__', "testpatch2"): self.assertEqual(foo.__module__, "testpatch2") - self.assertEqual(foo.__module__, 'mock.tests.testpatch') + self.assertEqual(foo.__module__, __name__) with patch.object(foo, '__annotations__', dict([('s', 1, )])): self.assertEqual(foo.__annotations__, dict([('s', 1, )])) From 324263d70b2ce2359467971c6bdcfc44c5a10fb0 Mon Sep 17 00:00:00 2001 From: infohash <46137868+infohash@users.noreply.github.com> Date: Sat, 9 Mar 2024 00:44:32 +0530 Subject: [PATCH 360/388] gh-75988: Fix issues with autospec ignoring wrapped object (#115223) * set default return value of functional types as _mock_return_value * added test of wrapping child attributes * added backward compatibility with explicit return * added docs on the order of precedence * added test to check default return_value Backports: 735fc2cbbcf875c359021b5b2af7f4c29f4cf66d Signed-off-by: Chris Withers --- ...4-02-27-13-05-51.gh-issue-75988.In6LlB.rst | 1 + mock/mock.py | 13 +++- mock/tests/testmock.py | 67 +++++++++++++++++++ 3 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 NEWS.d/2024-02-27-13-05-51.gh-issue-75988.In6LlB.rst diff --git a/NEWS.d/2024-02-27-13-05-51.gh-issue-75988.In6LlB.rst b/NEWS.d/2024-02-27-13-05-51.gh-issue-75988.In6LlB.rst new file mode 100644 index 00000000..682b7cfa --- /dev/null +++ b/NEWS.d/2024-02-27-13-05-51.gh-issue-75988.In6LlB.rst @@ -0,0 +1 @@ +Fixed :func:`unittest.mock.create_autospec` to pass the call through to the wrapped object to return the real result. diff --git a/mock/mock.py b/mock/mock.py index f4ccbb0a..034657db 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -575,7 +575,7 @@ def __get_return_value(self): if self._mock_delegate is not None: ret = self._mock_delegate.return_value - if ret is DEFAULT: + if ret is DEFAULT and self._mock_wraps is None: ret = self._get_child_mock( _new_parent=self, _new_name='()' ) @@ -1250,6 +1250,9 @@ def _execute_mock_call(_mock_self, *args, **kwargs): if self._mock_return_value is not DEFAULT: return self.return_value + if self._mock_delegate and self._mock_delegate.return_value is not DEFAULT: + return self.return_value + if self._mock_wraps is not None: return self._mock_wraps(*args, **kwargs) @@ -2837,9 +2840,12 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, if _parent is not None and not instance: _parent._mock_children[_name] = mock + wrapped = kwargs.get('wraps') + if is_type and not instance and 'return_value' not in kwargs: mock.return_value = create_autospec(spec, spec_set, instance=True, - _name='()', _parent=mock) + _name='()', _parent=mock, + wraps=wrapped) for entry in dir(spec): if _is_magic(entry): @@ -2861,6 +2867,9 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, continue kwargs = {'spec': original} + # Wrap child attributes also. + if wrapped and hasattr(wrapped, entry): + kwargs.update(wraps=original) if spec_set: kwargs = {'spec_set': original} diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index fffdf7c9..df971fe9 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -246,6 +246,65 @@ class B(object): with mock.patch('builtins.open', mock.mock_open()): mock.mock_open() # should still be valid with open() mocked + def test_create_autospec_wraps_class(self): + """Autospec a class with wraps & test if the call is passed to the + wrapped object.""" + result = "real result" + + class Result: + def get_result(self): + return result + class_mock = create_autospec(spec=Result, wraps=Result) + # Have to reassign the return_value to DEFAULT to return the real + # result (actual instance of "Result") when the mock is called. + class_mock.return_value = mock.DEFAULT + self.assertEqual(class_mock().get_result(), result) + # Autospec should also wrap child attributes of parent. + self.assertEqual(class_mock.get_result._mock_wraps, Result.get_result) + + def test_create_autospec_instance_wraps_class(self): + """Autospec a class instance with wraps & test if the call is passed + to the wrapped object.""" + result = "real result" + + class Result: + @staticmethod + def get_result(): + """This is a static method because when the mocked instance of + 'Result' will call this method, it won't be able to consume + 'self' argument.""" + return result + instance_mock = create_autospec(spec=Result, instance=True, wraps=Result) + # Have to reassign the return_value to DEFAULT to return the real + # result from "Result.get_result" when the mocked instance of "Result" + # calls "get_result". + instance_mock.get_result.return_value = mock.DEFAULT + self.assertEqual(instance_mock.get_result(), result) + # Autospec should also wrap child attributes of the instance. + self.assertEqual(instance_mock.get_result._mock_wraps, Result.get_result) + + def test_create_autospec_wraps_function_type(self): + """Autospec a function or a method with wraps & test if the call is + passed to the wrapped object.""" + result = "real result" + + class Result: + def get_result(self): + return result + func_mock = create_autospec(spec=Result.get_result, wraps=Result.get_result) + self.assertEqual(func_mock(Result()), result) + + def test_explicit_return_value_even_if_mock_wraps_object(self): + """If the mock has an explicit return_value set then calls are not + passed to the wrapped object and the return_value is returned instead. + """ + def my_func(): + return None + func_mock = create_autospec(spec=my_func, wraps=my_func) + return_value = "explicit return value" + func_mock.return_value = return_value + self.assertEqual(func_mock(), return_value) + def test_explicit_parent(self): parent = Mock() mock1 = Mock(parent=parent, return_value=None) @@ -623,6 +682,14 @@ def test_wraps_calls(self): real = Mock() mock = Mock(wraps=real) + # If "Mock" wraps an object, just accessing its + # "return_value" ("NonCallableMock.__get_return_value") should not + # trigger its descriptor ("NonCallableMock.__set_return_value") so + # the default "return_value" should always be "sentinel.DEFAULT". + self.assertEqual(mock.return_value, DEFAULT) + # It will not be "sentinel.DEFAULT" if the mock is not wrapping any + # object. + self.assertNotEqual(real.return_value, DEFAULT) self.assertEqual(mock(), real()) real.reset_mock() From f01306d28b371dea2d7236920aab53b28131650e Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 30 Apr 2024 17:23:44 +0300 Subject: [PATCH 361/388] gh-117860: Add tests for resolving names when import rebind names (GH-118176) Add tests for "import", pkgutil.resolve_name() and unittest.mock.path() for cases when "import a.b as x" and "from a import b as x" give different results. Backports: c0eaa232f63a62e0e0408911ab5f118dca2af607 Signed-off-by: Chris Withers --- mock/backports.py | 2 ++ mock/tests/testpatch.py | 66 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/mock/backports.py b/mock/backports.py index 6f20494c..87645a52 100644 --- a/mock/backports.py +++ b/mock/backports.py @@ -87,3 +87,5 @@ def run(self, result=None): from asyncio import iscoroutinefunction from unittest import IsolatedAsyncioTestCase + +from test.support.import_helper import DirsOnSysPath diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index eb9b2779..560adec3 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -7,6 +7,7 @@ from collections import OrderedDict import unittest +from mock.backports import DirsOnSysPath from mock.tests import support from mock.tests.support import SomeClass, is_instance @@ -1728,6 +1729,71 @@ def test(mock): 'exception traceback not propagated') + def test_name_resolution_import_rebinding(self): + # Currently mock.patch uses pkgutil.resolve_name(), but repeat + # similar tests just for the case. + # The same data is also used for testing import in test_import and + # pkgutil.resolve_name() in test_pkgutil. + path = os.path.join(os.path.dirname(test.__file__), 'test_import', 'data') + def check(name): + p = patch(name) + p.start() + p.stop() + def check_error(name): + p = patch(name) + self.assertRaises(AttributeError, p.start) + with uncache('package3', 'package3.submodule'), DirsOnSysPath(path): + check('package3.submodule.A.attr') + check_error('package3.submodule.B.attr') + with uncache('package3', 'package3.submodule'), DirsOnSysPath(path): + check('package3.submodule:A.attr') + check_error('package3.submodule:B.attr') + with uncache('package3', 'package3.submodule'), DirsOnSysPath(path): + check('package3:submodule.B.attr') + check_error('package3:submodule.A.attr') + check('package3.submodule.A.attr') + check_error('package3.submodule.B.attr') + check('package3:submodule.B.attr') + check_error('package3:submodule.A.attr') + with uncache('package3', 'package3.submodule'), DirsOnSysPath(path): + check('package3:submodule.B.attr') + check_error('package3:submodule.A.attr') + check('package3.submodule:A.attr') + check_error('package3.submodule:B.attr') + check('package3:submodule.B.attr') + check_error('package3:submodule.A.attr') + + def test_name_resolution_import_rebinding2(self): + path = os.path.join(os.path.dirname(test.__file__), 'test_import', 'data') + def check(name): + p = patch(name) + p.start() + p.stop() + def check_error(name): + p = patch(name) + self.assertRaises(AttributeError, p.start) + with uncache('package4', 'package4.submodule'), DirsOnSysPath(path): + check('package4.submodule.A.attr') + check_error('package4.submodule.B.attr') + with uncache('package4', 'package4.submodule'), DirsOnSysPath(path): + check('package4.submodule:A.attr') + check_error('package4.submodule:B.attr') + with uncache('package4', 'package4.submodule'), DirsOnSysPath(path): + check('package4:submodule.B.attr') + check_error('package4:submodule.A.attr') + check('package4.submodule.A.attr') + check_error('package4.submodule.B.attr') + check('package4:submodule.A.attr') + check_error('package4:submodule.B.attr') + with uncache('package4', 'package4.submodule'), DirsOnSysPath(path): + check('package4:submodule.B.attr') + check_error('package4:submodule.A.attr') + check('package4.submodule:A.attr') + check_error('package4.submodule:B.attr') + check('package4:submodule.A.attr') + check_error('package4:submodule.B.attr') + + def test_create_and_specs(self): for kwarg in ('spec', 'spec_set', 'autospec'): p = patch('%s.doesnotexist' % __name__, create=True, From 500944e557da0ab81fcfc879f8894f0d0fbf8fc8 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sat, 1 Mar 2025 18:02:32 +0000 Subject: [PATCH 362/388] Revert "gh-117860: Add tests for resolving names when import rebind names (GH-118176)" This reverts commit ac760fa2004d2673fb013da63da948434f468513. These test-only changes rely on a CPython source checkout --- mock/backports.py | 2 -- mock/tests/testpatch.py | 66 ----------------------------------------- 2 files changed, 68 deletions(-) diff --git a/mock/backports.py b/mock/backports.py index 87645a52..6f20494c 100644 --- a/mock/backports.py +++ b/mock/backports.py @@ -87,5 +87,3 @@ def run(self, result=None): from asyncio import iscoroutinefunction from unittest import IsolatedAsyncioTestCase - -from test.support.import_helper import DirsOnSysPath diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index 560adec3..eb9b2779 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -7,7 +7,6 @@ from collections import OrderedDict import unittest -from mock.backports import DirsOnSysPath from mock.tests import support from mock.tests.support import SomeClass, is_instance @@ -1729,71 +1728,6 @@ def test(mock): 'exception traceback not propagated') - def test_name_resolution_import_rebinding(self): - # Currently mock.patch uses pkgutil.resolve_name(), but repeat - # similar tests just for the case. - # The same data is also used for testing import in test_import and - # pkgutil.resolve_name() in test_pkgutil. - path = os.path.join(os.path.dirname(test.__file__), 'test_import', 'data') - def check(name): - p = patch(name) - p.start() - p.stop() - def check_error(name): - p = patch(name) - self.assertRaises(AttributeError, p.start) - with uncache('package3', 'package3.submodule'), DirsOnSysPath(path): - check('package3.submodule.A.attr') - check_error('package3.submodule.B.attr') - with uncache('package3', 'package3.submodule'), DirsOnSysPath(path): - check('package3.submodule:A.attr') - check_error('package3.submodule:B.attr') - with uncache('package3', 'package3.submodule'), DirsOnSysPath(path): - check('package3:submodule.B.attr') - check_error('package3:submodule.A.attr') - check('package3.submodule.A.attr') - check_error('package3.submodule.B.attr') - check('package3:submodule.B.attr') - check_error('package3:submodule.A.attr') - with uncache('package3', 'package3.submodule'), DirsOnSysPath(path): - check('package3:submodule.B.attr') - check_error('package3:submodule.A.attr') - check('package3.submodule:A.attr') - check_error('package3.submodule:B.attr') - check('package3:submodule.B.attr') - check_error('package3:submodule.A.attr') - - def test_name_resolution_import_rebinding2(self): - path = os.path.join(os.path.dirname(test.__file__), 'test_import', 'data') - def check(name): - p = patch(name) - p.start() - p.stop() - def check_error(name): - p = patch(name) - self.assertRaises(AttributeError, p.start) - with uncache('package4', 'package4.submodule'), DirsOnSysPath(path): - check('package4.submodule.A.attr') - check_error('package4.submodule.B.attr') - with uncache('package4', 'package4.submodule'), DirsOnSysPath(path): - check('package4.submodule:A.attr') - check_error('package4.submodule:B.attr') - with uncache('package4', 'package4.submodule'), DirsOnSysPath(path): - check('package4:submodule.B.attr') - check_error('package4:submodule.A.attr') - check('package4.submodule.A.attr') - check_error('package4.submodule.B.attr') - check('package4:submodule.A.attr') - check_error('package4:submodule.B.attr') - with uncache('package4', 'package4.submodule'), DirsOnSysPath(path): - check('package4:submodule.B.attr') - check_error('package4:submodule.A.attr') - check('package4.submodule:A.attr') - check_error('package4.submodule:B.attr') - check('package4:submodule.A.attr') - check_error('package4:submodule.B.attr') - - def test_create_and_specs(self): for kwarg in ('spec', 'spec_set', 'autospec'): p = patch('%s.doesnotexist' % __name__, create=True, From a734350ab7d9aa7292d91a4d2cf1e55b377de65e Mon Sep 17 00:00:00 2001 From: infohash <46137868+infohash@users.noreply.github.com> Date: Thu, 2 May 2024 23:06:35 +0530 Subject: [PATCH 363/388] gh-90848: Fixed create_autospec ignoring configure_mock style kwargs (#118163) Backports: b28a3339e4c63ea3a801dba9bbbc6af5af42c3a0 Signed-off-by: Chris Withers --- ...4-04-22-21-54-12.gh-issue-90848.5jHEEc.rst | 1 + mock/mock.py | 20 +++++++++++-------- mock/tests/testmock.py | 13 ++++++++++++ 3 files changed, 26 insertions(+), 8 deletions(-) create mode 100644 NEWS.d/2024-04-22-21-54-12.gh-issue-90848.5jHEEc.rst diff --git a/NEWS.d/2024-04-22-21-54-12.gh-issue-90848.5jHEEc.rst b/NEWS.d/2024-04-22-21-54-12.gh-issue-90848.5jHEEc.rst new file mode 100644 index 00000000..adbca012 --- /dev/null +++ b/NEWS.d/2024-04-22-21-54-12.gh-issue-90848.5jHEEc.rst @@ -0,0 +1 @@ +Fixed :func:`unittest.mock.create_autospec` to configure parent mock with keyword arguments. diff --git a/mock/mock.py b/mock/mock.py index 034657db..bd816b6e 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2840,8 +2840,8 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, if _parent is not None and not instance: _parent._mock_children[_name] = mock - wrapped = kwargs.get('wraps') - + # Pop wraps from kwargs because it must not be passed to configure_mock. + wrapped = kwargs.pop('wraps', None) if is_type and not instance and 'return_value' not in kwargs: mock.return_value = create_autospec(spec, spec_set, instance=True, _name='()', _parent=mock, @@ -2866,12 +2866,12 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, except AttributeError: continue - kwargs = {'spec': original} + child_kwargs = {'spec': original} # Wrap child attributes also. if wrapped and hasattr(wrapped, entry): - kwargs.update(wraps=original) + child_kwargs.update(wraps=original) if spec_set: - kwargs = {'spec_set': original} + child_kwargs = {'spec_set': original} if not isinstance(original, FunctionTypes): new = _SpecState(original, spec_set, mock, entry, instance) @@ -2882,14 +2882,13 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, parent = mock.mock skipfirst = _must_skip(spec, entry, is_type) - kwargs['_eat_self'] = skipfirst + child_kwargs['_eat_self'] = skipfirst if iscoroutinefunction(original): child_klass = AsyncMock else: child_klass = MagicMock new = child_klass(parent=parent, name=entry, _new_name=entry, - _new_parent=parent, - **kwargs) + _new_parent=parent, **child_kwargs) mock._mock_children[entry] = new new.return_value = child_klass() _check_signature(original, new, skipfirst=skipfirst) @@ -2900,6 +2899,11 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, # setting as an instance attribute? if isinstance(new, FunctionTypes): setattr(mock, entry, new) + # kwargs are passed with respect to the parent mock so, they are not used + # for creating return_value of the parent mock. So, this condition + # should be true only for the parent mock if kwargs are given. + if _is_instance_mock(mock) and kwargs: + mock.configure_mock(**kwargs) return mock diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index df971fe9..9d07670a 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -116,6 +116,19 @@ def f(): pass with self.assertRaises(TypeError): mock() + def test_create_autospec_should_be_configurable_by_kwargs(self): + """If kwargs are given to configure mock, the function must configure + the parent mock during initialization.""" + mocked_result = 'mocked value' + class_mock = create_autospec(spec=Something, **{ + 'return_value.meth.side_effect': [ValueError, DEFAULT], + 'return_value.meth.return_value': mocked_result}) + with self.assertRaises(ValueError): + class_mock().meth(a=None, b=None, c=None) + self.assertEqual(class_mock().meth(a=None, b=None, c=None), mocked_result) + # Only the parent mock should be configurable because the user will + # pass kwargs with respect to the parent mock. + self.assertEqual(class_mock().return_value.meth.side_effect, None) def test_repr(self): mock = Mock(name='foo') From 72318d8f13d1a489bef7e1ab7c7a768e4831bcef Mon Sep 17 00:00:00 2001 From: Geoffrey Thomas Date: Wed, 22 May 2024 12:35:18 -0400 Subject: [PATCH 364/388] Remove almost all unpaired backticks in docstrings (#119231) As reported in #117847 and #115366, an unpaired backtick in a docstring tends to confuse e.g. Sphinx running on subclasses of standard library objects, and the typographic style of using a backtick as an opening quote is no longer in favor. Convert almost all uses of the form The variable `foo' should do xyz to The variable 'foo' should do xyz and also fix up miscellaneous other unpaired backticks (extraneous / missing characters). No functional change is intended here other than in human-readable docstrings. Backports: ef172521a9e9dfadebe57d590bfb53a0e9ac3a0b Signed-off-by: Chris Withers --- mock/mock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock/mock.py b/mock/mock.py index bd816b6e..a4171c68 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1784,7 +1784,7 @@ def patch( the patch is undone. If `new` is omitted, then the target is replaced with an - `AsyncMock if the patched object is an async function or a + `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 From bccda22e3c0a1b7a68d573512b925207473d42d3 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Tue, 11 Jun 2024 07:41:12 +0200 Subject: [PATCH 365/388] gh-119600: mock: do not access attributes of original when new_callable is set (#119601) In order to patch flask.g e.g. as in #84982, that proxies getattr must not be invoked. For that, mock must not try to read from the original object. In some cases that is unavoidable, e.g. when doing autospec. However, patch("flask.g", new_callable=MagicMock) should be entirely safe. Backports: 422c4fc855afd18bcc6415902ea1d85a50cb7ce1 Signed-off-by: Chris Withers --- .../2024-06-10-14-00-40.gh-issue-119600.jJMf4C.rst | 2 ++ mock/mock.py | 14 +++++++++----- mock/tests/support.py | 11 +++++++++++ mock/tests/testpatch.py | 7 +++++++ 4 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 NEWS.d/2024-06-10-14-00-40.gh-issue-119600.jJMf4C.rst diff --git a/NEWS.d/2024-06-10-14-00-40.gh-issue-119600.jJMf4C.rst b/NEWS.d/2024-06-10-14-00-40.gh-issue-119600.jJMf4C.rst new file mode 100644 index 00000000..04c9ca9c --- /dev/null +++ b/NEWS.d/2024-06-10-14-00-40.gh-issue-119600.jJMf4C.rst @@ -0,0 +1,2 @@ +Fix :func:`unittest.mock.patch` to not read attributes of the target when +``new_callable`` is set. Patch by Robert Collins. diff --git a/mock/mock.py b/mock/mock.py index a4171c68..caf7e4d4 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1543,13 +1543,12 @@ def __enter__(self): if isinstance(original, type): # If we're patching out a class and there is a spec inherit = True - if spec is None and _is_async_obj(original): - Klass = AsyncMock - else: - Klass = MagicMock - _kwargs = {} + + # Determine the Klass to use if new_callable is not None: Klass = new_callable + elif spec is None and _is_async_obj(original): + Klass = AsyncMock elif spec is not None or spec_set is not None: this_spec = spec if spec_set is not None: @@ -1562,7 +1561,12 @@ def __enter__(self): Klass = AsyncMock elif not_callable: Klass = NonCallableMagicMock + else: + Klass = MagicMock + else: + Klass = MagicMock + _kwargs = {} if spec is not None: _kwargs['spec'] = spec if spec_set is not None: diff --git a/mock/tests/support.py b/mock/tests/support.py index 85fd0a31..19548747 100644 --- a/mock/tests/support.py +++ b/mock/tests/support.py @@ -18,6 +18,17 @@ def wibble(self): pass class X(object): pass +# A standin for weurkzeug.local.LocalProxy - issue 119600 +def _inaccessible(*args, **kwargs): + raise AttributeError + + +class OpaqueProxy: + __getattribute__ = _inaccessible + + +g = OpaqueProxy() + @contextlib.contextmanager def uncache(*names): diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index eb9b2779..903e8bda 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -1978,6 +1978,13 @@ def test(): pass with self.assertRaises(TypeError): test() + def test_patch_proxy_object(self): + @patch("mock.tests.support.g", new_callable=MagicMock()) + def test(_): + pass + + test() + if __name__ == '__main__': unittest.main() From 37a17680767856145eedfd80039b11762f7d6ccd Mon Sep 17 00:00:00 2001 From: blhsing Date: Tue, 11 Jun 2024 13:42:49 +0800 Subject: [PATCH 366/388] gh-65454: avoid triggering call to a PropertyMock in NonCallableMock.__setattr__ (#120019) Backports: 9e9ee50421c857b443e2060274f17fb884d54473 Signed-off-by: Chris Withers --- NEWS.d/2024-06-04-08-57-02.gh-issue-65454.o9j4wF.rst | 1 + mock/mock.py | 3 +++ mock/tests/testhelpers.py | 8 ++++++++ 3 files changed, 12 insertions(+) create mode 100644 NEWS.d/2024-06-04-08-57-02.gh-issue-65454.o9j4wF.rst diff --git a/NEWS.d/2024-06-04-08-57-02.gh-issue-65454.o9j4wF.rst b/NEWS.d/2024-06-04-08-57-02.gh-issue-65454.o9j4wF.rst new file mode 100644 index 00000000..0b232cf8 --- /dev/null +++ b/NEWS.d/2024-06-04-08-57-02.gh-issue-65454.o9j4wF.rst @@ -0,0 +1 @@ +:func:`unittest.mock.Mock.attach_mock` no longer triggers a call to a ``PropertyMock`` being attached. diff --git a/mock/mock.py b/mock/mock.py index caf7e4d4..1d86da52 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -832,6 +832,9 @@ def __setattr__(self, name, value): mock_name = f'{self._extract_mock_name()}.{name}' raise AttributeError(f'Cannot set {mock_name}') + if isinstance(value, PropertyMock): + self.__dict__[name] = value + return return object.__setattr__(self, name, value) diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index def8450e..749bdfdc 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -1136,6 +1136,14 @@ def test_propertymock_side_effect(self): p.assert_called_once_with() + def test_propertymock_attach(self): + m = Mock() + p = PropertyMock() + type(m).foo = p + m.attach_mock(p, 'foo') + self.assertEqual(m.mock_calls, []) + + class TestCallablePredicate(unittest.TestCase): def test_type(self): From f6a29424ea4272ce46181e451d854bc88dce2b41 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Wed, 19 Jun 2024 23:35:11 +0300 Subject: [PATCH 367/388] gh-120732: Fix `name` passing to `Mock`, when using kwargs to `create_autospec` (#120737) Backports: 1e4815692f6c8a37a3974d0d7d2025494d026d76 Signed-off-by: Chris Withers --- .../2024-06-19-15-06-58.gh-issue-120732.OvYV9b.rst | 2 ++ mock/mock.py | 13 ++++++------- mock/tests/testmock.py | 5 +++++ 3 files changed, 13 insertions(+), 7 deletions(-) create mode 100644 NEWS.d/2024-06-19-15-06-58.gh-issue-120732.OvYV9b.rst diff --git a/NEWS.d/2024-06-19-15-06-58.gh-issue-120732.OvYV9b.rst b/NEWS.d/2024-06-19-15-06-58.gh-issue-120732.OvYV9b.rst new file mode 100644 index 00000000..e31c4dd3 --- /dev/null +++ b/NEWS.d/2024-06-19-15-06-58.gh-issue-120732.OvYV9b.rst @@ -0,0 +1,2 @@ +Fix ``name`` passing to :class:`unittest.mock.Mock` object when using +:func:`unittest.mock.create_autospec`. diff --git a/mock/mock.py b/mock/mock.py index 1d86da52..20c8476b 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2807,6 +2807,12 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, if not unsafe: _check_spec_arg_typos(kwargs) + _name = kwargs.pop('name', _name) + _new_name = _name + if _parent is None: + # for a top level object no _new_name should be set + _new_name = '' + _kwargs.update(kwargs) Klass = MagicMock @@ -2824,13 +2830,6 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, elif is_type and instance and not _instance_callable(spec): Klass = NonCallableMagicMock - _name = _kwargs.pop('name', _name) - - _new_name = _name - if _parent is None: - # for a top level object no _new_name should be set - _new_name = '' - mock = Klass(parent=_parent, _new_parent=_parent, _new_name=_new_name, name=_name, **_kwargs) diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 9d07670a..daf82b88 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -130,6 +130,11 @@ def test_create_autospec_should_be_configurable_by_kwargs(self): # pass kwargs with respect to the parent mock. self.assertEqual(class_mock().return_value.meth.side_effect, None) + def test_create_autospec_correctly_handles_name(self): + class X: ... + mock = create_autospec(X, spec_set=True, name="Y") + self.assertEqual(mock._mock_name, "Y") + def test_repr(self): mock = Mock(name='foo') self.assertIn('foo', repr(mock)) From c92d8024e6c5488fc01ce47d0d373d01d7591218 Mon Sep 17 00:00:00 2001 From: Dominic H Date: Mon, 15 Jul 2024 09:14:17 +0200 Subject: [PATCH 368/388] gh-117765: Improve documentation for `mocker.patch.dict` (#121755) Backports: 8303d32ff55945c5b38eeeaf1b1811dbcf8aa9be Signed-off-by: Chris Withers --- NEWS.d/2024-07-14-12-25-53.gh-issue-117765.YFMOUv.rst | 1 + mock/mock.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 NEWS.d/2024-07-14-12-25-53.gh-issue-117765.YFMOUv.rst diff --git a/NEWS.d/2024-07-14-12-25-53.gh-issue-117765.YFMOUv.rst b/NEWS.d/2024-07-14-12-25-53.gh-issue-117765.YFMOUv.rst new file mode 100644 index 00000000..a727c1aa --- /dev/null +++ b/NEWS.d/2024-07-14-12-25-53.gh-issue-117765.YFMOUv.rst @@ -0,0 +1 @@ +Improved documentation for :func:`unittest.mock.patch.dict` diff --git a/mock/mock.py b/mock/mock.py index 20c8476b..bd000ca0 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1865,7 +1865,8 @@ def patch( class _patch_dict(object): """ Patch a dictionary, or dictionary like object, and restore the dictionary - to its original state after the test. + to its original state after the test, where the restored dictionary is + a copy of the dictionary as it was before the test. `in_dict` can be a dictionary or a mapping like container. If it is a mapping then it must at least support getting, setting and deleting items From bba57b218af5af348a7ca7e536e3ea52b9dc86c4 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 3 Mar 2025 10:01:42 +0000 Subject: [PATCH 369/388] ensure we always use iscoroutinefunction from backports in non-test code --- mock/mock.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mock/mock.py b/mock/mock.py index bd000ca0..6f864684 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1410,7 +1410,7 @@ def copy(self): def __call__(self, func): if isinstance(func, type): return self.decorate_class(func) - if inspect.iscoroutinefunction(func): + if iscoroutinefunction(func): return self.decorate_async_callable(func) return self.decorate_callable(func) @@ -1904,7 +1904,7 @@ def __init__(self, in_dict, values=(), clear=False, **kwargs): def __call__(self, f): if isinstance(f, type): return self.decorate_class(f) - if inspect.iscoroutinefunction(f): + if iscoroutinefunction(f): return self.decorate_async_callable(f) return self.decorate_callable(f) From 0abbacd4749913c22d85d6135801728da8bf79d8 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 3 Mar 2025 10:05:15 +0000 Subject: [PATCH 370/388] Use iscoroutinefunction and IsolatedAsyncioTestCase when importable ...rather than by python version --- mock/backports.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/mock/backports.py b/mock/backports.py index 6f20494c..34f5c5ea 100644 --- a/mock/backports.py +++ b/mock/backports.py @@ -1,12 +1,13 @@ import sys -if sys.version_info[:2] < (3, 8): +try: + from asyncio import iscoroutinefunction +except ImportError: - import asyncio, functools + import 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): @@ -35,6 +36,13 @@ def iscoroutinefunction(obj): ) +try: + from unittest import IsolatedAsyncioTestCase +except ImportError: + import asyncio + from unittest import TestCase + + class IsolatedAsyncioTestCase(TestCase): def __init__(self, methodName='runTest'): @@ -80,10 +88,3 @@ def run(self, result=None): return super().run(result) finally: self._tearDownAsyncioLoop() - - -else: - - from asyncio import iscoroutinefunction - from unittest import IsolatedAsyncioTestCase - From 9c9bc0f1dce2e1bde84c56bb6da72a871704996d Mon Sep 17 00:00:00 2001 From: Wulian <1055917385@qq.com> Date: Mon, 12 Aug 2024 00:35:51 +0800 Subject: [PATCH 371/388] gh-122858: Deprecate `asyncio.iscoroutinefunction` (#122875) Deprecate `asyncio.iscoroutinefunction` in favor of `inspect.iscoroutinefunction`. Co-authored-by: Kumar Aditya Backports: bc9d92c67933917b474e61905451c6408c68e71d Signed-off-by: Chris Withers --- NEWS.d/2024-08-10-10-21-44.gh-issue-122858.ZC1rJD.rst | 2 ++ mock/backports.py | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 NEWS.d/2024-08-10-10-21-44.gh-issue-122858.ZC1rJD.rst diff --git a/NEWS.d/2024-08-10-10-21-44.gh-issue-122858.ZC1rJD.rst b/NEWS.d/2024-08-10-10-21-44.gh-issue-122858.ZC1rJD.rst new file mode 100644 index 00000000..d452ad6a --- /dev/null +++ b/NEWS.d/2024-08-10-10-21-44.gh-issue-122858.ZC1rJD.rst @@ -0,0 +1,2 @@ +Deprecate :func:`!asyncio.iscoroutinefunction` in favor of +:func:`inspect.iscoroutinefunction`. diff --git a/mock/backports.py b/mock/backports.py index 34f5c5ea..f2563604 100644 --- a/mock/backports.py +++ b/mock/backports.py @@ -1,9 +1,17 @@ import sys +iscoroutinefunction = None + try: - from asyncio import iscoroutinefunction + from inspect import iscoroutinefunction except ImportError: + try: + from asyncio import iscoroutinefunction + except ImportError: + pass + +if iscoroutinefunction is None: import functools from asyncio.coroutines import _is_coroutine From a33b0f4f8a9b2b5c6890c5317de133bc2e272992 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 3 Mar 2025 10:16:27 +0000 Subject: [PATCH 372/388] Adjust source of iscoroutinefunction is backports Selecting the source by version here appears to keep the tests passing on more versions --- mock/backports.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/mock/backports.py b/mock/backports.py index f2563604..c268a3c2 100644 --- a/mock/backports.py +++ b/mock/backports.py @@ -1,17 +1,11 @@ import sys -iscoroutinefunction = None - -try: +if sys.version_info[:2] > (3, 9): from inspect import iscoroutinefunction -except ImportError: - try: - from asyncio import iscoroutinefunction - except ImportError: - pass - -if iscoroutinefunction is None: +elif sys.version_info[:2] >= (3, 8): + from asyncio import iscoroutinefunction +else: import functools from asyncio.coroutines import _is_coroutine From fd8e4d18d8d4c41feb288335bf0ab6aff241522f Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 3 Mar 2025 10:20:28 +0000 Subject: [PATCH 373/388] Backports: e6d5ff55d0816d7f5eb45e49c810e936b09d2be7, skipped: We have to handle iscoroutinefunction differently --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index 06e5d6af..1730b693 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -e6379f72cbc60f6b3c5676f9e225d4f145d5693f +e6d5ff55d0816d7f5eb45e49c810e936b09d2be7 From f0a258a64d9787a55a3f8f0b4abe37175c8d60f9 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 14 Sep 2024 13:20:44 +0300 Subject: [PATCH 374/388] Remove unused variable in `MagicMixin._mock_set_magics` (#124092) Backports: 1de46136b916736487019c2f78af2bf0cadd7ecd 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 6f864684..b5ce6418 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2206,8 +2206,6 @@ def _mock_set_magics(self): if getattr(self, "_mock_methods", None) is not None: these_magics = orig_magics.intersection(self._mock_methods) - - remove_magics = set() remove_magics = orig_magics - these_magics for entry in remove_magics: From 6345bd08ab7d9831fe96b2ca99752a25a80c3932 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 19 Sep 2024 10:55:47 +0300 Subject: [PATCH 375/388] gh-123934: Fix `MagicMock` not to reset magic method return values (#124038) Backports: 7628f67d55cb65bad9c9266e0457e468cd7e3775 Signed-off-by: Chris Withers --- ...-09-13-10-34-19.gh-issue-123934.yMe7mL.rst | 2 + mock/mock.py | 13 ++++++- mock/tests/testmagicmethods.py | 39 +++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 NEWS.d/2024-09-13-10-34-19.gh-issue-123934.yMe7mL.rst diff --git a/NEWS.d/2024-09-13-10-34-19.gh-issue-123934.yMe7mL.rst b/NEWS.d/2024-09-13-10-34-19.gh-issue-123934.yMe7mL.rst new file mode 100644 index 00000000..cec7741b --- /dev/null +++ b/NEWS.d/2024-09-13-10-34-19.gh-issue-123934.yMe7mL.rst @@ -0,0 +1,2 @@ +Fix :class:`unittest.mock.MagicMock` reseting magic methods return values +after ``.reset_mock(return_value=True)`` was called. diff --git a/mock/mock.py b/mock/mock.py index b5ce6418..0b3d5bd5 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -630,7 +630,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 = [] @@ -2258,6 +2258,17 @@ def mock_add_spec(self, spec, spec_set=False): self._mock_add_spec(spec, spec_set) self._mock_set_magics() + def reset_mock(self, *args, return_value=False, **kwargs): + if ( + return_value + and self._mock_name + and _is_magic(self._mock_name) + ): + # Don't reset return values for magic methods, + # otherwise `m.__str__` will start + # to return `MagicMock` instances, instead of `str` instances. + return_value = False + super().reset_mock(*args, return_value=return_value, **kwargs) class MagicProxy(Base): diff --git a/mock/tests/testmagicmethods.py b/mock/tests/testmagicmethods.py index e1f1ee0e..7e242f4a 100644 --- a/mock/tests/testmagicmethods.py +++ b/mock/tests/testmagicmethods.py @@ -331,6 +331,45 @@ def test_magic_methods_fspath(self): self.assertEqual(os.fspath(mock), expected_path) mock.__fspath__.assert_called_once() + def test_magic_mock_does_not_reset_magic_returns(self): + # https://github.com/python/cpython/issues/123934 + for reset in (True, False): + with self.subTest(reset=reset): + mm = MagicMock() + self.assertIs(type(mm.__str__()), str) + mm.__str__.assert_called_once() + + self.assertIs(type(mm.__hash__()), int) + mm.__hash__.assert_called_once() + + for _ in range(3): + # Repeat reset several times to be sure: + mm.reset_mock(return_value=reset) + + self.assertIs(type(mm.__str__()), str) + mm.__str__.assert_called_once() + + self.assertIs(type(mm.__hash__()), int) + mm.__hash__.assert_called_once() + + def test_magic_mock_resets_manual_mocks(self): + mm = MagicMock() + mm.__iter__ = MagicMock(return_value=iter([1])) + mm.custom = MagicMock(return_value=2) + self.assertEqual(list(iter(mm)), [1]) + self.assertEqual(mm.custom(), 2) + + mm.reset_mock(return_value=True) + self.assertEqual(list(iter(mm)), []) + self.assertIsInstance(mm.custom(), MagicMock) + + def test_magic_mock_resets_manual_mocks_empty_iter(self): + mm = MagicMock() + mm.__iter__.return_value = [] + self.assertEqual(list(iter(mm)), []) + + mm.reset_mock(return_value=True) + self.assertEqual(list(iter(mm)), []) def test_magic_methods_and_spec(self): class Iterable(object): From 6b8770ea3f4e095febcf71a30e32a5dc333d28a0 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 26 Sep 2024 15:06:52 +0300 Subject: [PATCH 376/388] gh-124234: Improve docs for `Mock.reset_mock` (#124237) Backports: 19fed6cf6eb51044fd0c02c6338259e2dd7fd462 Signed-off-by: Chris Withers --- mock/mock.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mock/mock.py b/mock/mock.py index 0b3d5bd5..bf1768f0 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -630,7 +630,9 @@ 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: bool = False, + side_effect: bool = False): "Restore the mock object to its initial state." if visited is None: visited = [] @@ -2258,7 +2260,7 @@ def mock_add_spec(self, spec, spec_set=False): self._mock_add_spec(spec, spec_set) self._mock_set_magics() - def reset_mock(self, *args, return_value=False, **kwargs): + def reset_mock(self, *args, return_value: bool = False, **kwargs): if ( return_value and self._mock_name From 7a018f1bab1a4d6c4d19c2d1f89d3efa10ab2712 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 27 Sep 2024 09:48:31 +0300 Subject: [PATCH 377/388] gh-124176: Add special support for dataclasses to `create_autospec` (#124429) Backports: 3a0e7f57628466aedcaaf6c5ff7c8224f5155a2c Signed-off-by: Chris Withers --- ...-09-24-13-32-16.gh-issue-124176.6hmOPz.rst | 4 + mock/mock.py | 28 +++++-- mock/tests/testhelpers.py | 81 +++++++++++++++++++ 3 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 NEWS.d/2024-09-24-13-32-16.gh-issue-124176.6hmOPz.rst diff --git a/NEWS.d/2024-09-24-13-32-16.gh-issue-124176.6hmOPz.rst b/NEWS.d/2024-09-24-13-32-16.gh-issue-124176.6hmOPz.rst new file mode 100644 index 00000000..38c03066 --- /dev/null +++ b/NEWS.d/2024-09-24-13-32-16.gh-issue-124176.6hmOPz.rst @@ -0,0 +1,4 @@ +Add support for :func:`dataclasses.dataclass` in +:func:`unittest.mock.create_autospec`. Now ``create_autospec`` will check +for potential dataclasses and use :func:`dataclasses.fields` function to +retrieve the spec information. diff --git a/mock/mock.py b/mock/mock.py index bf1768f0..aa9b1f22 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -32,6 +32,13 @@ import sys import threading import builtins + +try: + from dataclasses import fields, is_dataclass + HAS_DATACLASSES = True +except ImportError: + HAS_DATACLASSES = False + from types import CodeType, ModuleType, MethodType from unittest.util import safe_repr from functools import wraps, partial @@ -2808,7 +2815,15 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, raise InvalidSpecError(f'Cannot autospec a Mock object. ' f'[object={spec!r}]') is_async_func = _is_async_func(spec) - _kwargs = {'spec': spec} + + entries = [(entry, _missing) for entry in dir(spec)] + if is_type and instance and HAS_DATACLASSES and is_dataclass(spec): + dataclass_fields = fields(spec) + entries.extend((f.name, f.type) for f in dataclass_fields) + _kwargs = {'spec': [f.name for f in dataclass_fields]} + else: + _kwargs = {'spec': spec} + if spec_set: _kwargs = {'spec_set': spec} elif spec is None: @@ -2865,7 +2880,7 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, _name='()', _parent=mock, wraps=wrapped) - for entry in dir(spec): + for entry, original in entries: if _is_magic(entry): # MagicMock already does the useful magic methods for us continue @@ -2879,10 +2894,11 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, # AttributeError on being fetched? # we could be resilient against it, or catch and propagate the # exception when the attribute is fetched from the mock - try: - original = getattr(spec, entry) - except AttributeError: - continue + if original is _missing: + try: + original = getattr(spec, entry) + except AttributeError: + continue child_kwargs = {'spec': original} # Wrap child attributes also. diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index 749bdfdc..be57bfb0 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -10,10 +10,17 @@ from mock.mock import _Call, _CallList, _callable from mock import IS_PYPY +try: + from dataclasses import dataclass, field, InitVar +except ImportError: + pass + from datetime import datetime from functools import partial +from typing import ClassVar import pytest +import sys class SomeClass(object): @@ -1043,6 +1050,80 @@ def f(a): pass self.assertEqual(mock.mock_calls, []) self.assertEqual(rv.mock_calls, []) + @pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python 3.7 or higher") + def test_dataclass_post_init(self): + @dataclass + class WithPostInit: + a: int = field(init=False) + b: int = field(init=False) + def __post_init__(self): + self.a = 1 + self.b = 2 + + for mock in [ + create_autospec(WithPostInit, instance=True), + create_autospec(WithPostInit()), + ]: + with self.subTest(mock=mock): + self.assertIsInstance(mock.a, int) + self.assertIsInstance(mock.b, int) + + # Classes do not have these fields: + mock = create_autospec(WithPostInit) + msg = "Mock object has no attribute" + with self.assertRaisesRegex(AttributeError, msg): + mock.a + with self.assertRaisesRegex(AttributeError, msg): + mock.b + + @pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python 3.7 or higher") + def test_dataclass_default(self): + @dataclass + class WithDefault: + a: int + b: int = 0 + + for mock in [ + create_autospec(WithDefault, instance=True), + create_autospec(WithDefault(1)), + ]: + with self.subTest(mock=mock): + self.assertIsInstance(mock.a, int) + self.assertIsInstance(mock.b, int) + + @pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python 3.7 or higher") + def test_dataclass_with_method(self): + @dataclass + class WithMethod: + a: int + def b(self) -> int: + return 1 + + for mock in [ + create_autospec(WithMethod, instance=True), + create_autospec(WithMethod(1)), + ]: + with self.subTest(mock=mock): + self.assertIsInstance(mock.a, int) + mock.b.assert_not_called() + + @pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python 3.7 or higher") + def test_dataclass_with_non_fields(self): + @dataclass + class WithNonFields: + a: ClassVar[int] + b: InitVar[int] + + msg = "Mock object has no attribute" + for mock in [ + create_autospec(WithNonFields, instance=True), + create_autospec(WithNonFields(1)), + ]: + with self.subTest(mock=mock): + with self.assertRaisesRegex(AttributeError, msg): + mock.a + with self.assertRaisesRegex(AttributeError, msg): + mock.b class TestCallList(unittest.TestCase): From cc4b0ec60062cf96eb5104536d829031d49b0f18 Mon Sep 17 00:00:00 2001 From: Red4Ru <39802734+Red4Ru@users.noreply.github.com> Date: Wed, 13 Nov 2024 11:20:38 +0300 Subject: [PATCH 378/388] gh-104745: Limit starting a patcher more than once without stopping it (#126649) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, this would cause an `AttributeError` if the patch stopped more than once after this, and would also disrupt the original patched object. --------- Co-authored-by: Peter Bierma Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> Backports: 1e40c5ba47780ddd91868abb3aa064f5ba3015e4 Signed-off-by: Chris Withers --- ...-11-10-18-14-51.gh-issue-104745.zAa5Ke.rst | 3 ++ mock/mock.py | 9 ++++ mock/tests/testpatch.py | 52 ++++++++++++++++++- 3 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 NEWS.d/2024-11-10-18-14-51.gh-issue-104745.zAa5Ke.rst diff --git a/NEWS.d/2024-11-10-18-14-51.gh-issue-104745.zAa5Ke.rst b/NEWS.d/2024-11-10-18-14-51.gh-issue-104745.zAa5Ke.rst new file mode 100644 index 00000000..c83a1076 --- /dev/null +++ b/NEWS.d/2024-11-10-18-14-51.gh-issue-104745.zAa5Ke.rst @@ -0,0 +1,3 @@ +Limit starting a patcher (from :func:`unittest.mock.patch` or +:func:`unittest.mock.patch.object`) more than +once without stopping it diff --git a/mock/mock.py b/mock/mock.py index aa9b1f22..37ee85aa 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -1401,6 +1401,7 @@ def __init__( self.autospec = autospec self.kwargs = kwargs self.additional_patchers = [] + self.is_started = False def copy(self): @@ -1513,6 +1514,9 @@ def get_original(self): def __enter__(self): """Perform the patch.""" + if self.is_started: + raise RuntimeError("Patch is already started") + new, spec, spec_set = self.new, self.spec, self.spec_set autospec, kwargs = self.autospec, self.kwargs new_callable = self.new_callable @@ -1644,6 +1648,7 @@ def __enter__(self): self.temp_original = original self.is_local = local self._exit_stack = contextlib.ExitStack() + self.is_started = True try: setattr(self.target, self.attribute, new_attr) if self.attribute_name is not None: @@ -1663,6 +1668,9 @@ def __enter__(self): def __exit__(self, *exc_info): """Undo the patch.""" + if not self.is_started: + return + if self.is_local and self.temp_original is not DEFAULT: setattr(self.target, self.attribute, self.temp_original) else: @@ -1679,6 +1687,7 @@ def __exit__(self, *exc_info): del self.target exit_stack = self._exit_stack del self._exit_stack + self.is_started = False return exit_stack.__exit__(*exc_info) diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index 903e8bda..a8924022 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -743,6 +743,54 @@ def test_stop_idempotent(self): self.assertIsNone(patcher.stop()) + def test_exit_idempotent(self): + patcher = patch(foo_name, 'bar', 3) + with patcher: + patcher.stop() + + + def test_second_start_failure(self): + patcher = patch(foo_name, 'bar', 3) + patcher.start() + try: + self.assertRaises(RuntimeError, patcher.start) + finally: + patcher.stop() + + + def test_second_enter_failure(self): + patcher = patch(foo_name, 'bar', 3) + with patcher: + self.assertRaises(RuntimeError, patcher.start) + + + def test_second_start_after_stop(self): + patcher = patch(foo_name, 'bar', 3) + patcher.start() + patcher.stop() + patcher.start() + patcher.stop() + + + def test_property_setters(self): + mock_object = Mock() + mock_bar = mock_object.bar + patcher = patch.object(mock_object, 'bar', 'x') + with patcher: + self.assertEqual(patcher.is_local, False) + self.assertIs(patcher.target, mock_object) + self.assertEqual(patcher.temp_original, mock_bar) + patcher.is_local = True + patcher.target = mock_bar + patcher.temp_original = mock_object + self.assertEqual(patcher.is_local, True) + self.assertIs(patcher.target, mock_bar) + self.assertEqual(patcher.temp_original, mock_object) + # if changes are left intact, they may lead to disruption as shown below (it might be what someone needs though) + self.assertEqual(mock_bar.bar, mock_object) + self.assertEqual(mock_object.bar, 'x') + + def test_patchobject_start_stop(self): original = something patcher = patch.object(PTModule, 'something', 'foo') @@ -1096,7 +1144,7 @@ def test_new_callable_patch(self): self.assertIsNot(m1, m2) for mock in m1, m2: - self.assertNotCallable(m1) + self.assertNotCallable(mock) def test_new_callable_patch_object(self): @@ -1109,7 +1157,7 @@ def test_new_callable_patch_object(self): self.assertIsNot(m1, m2) for mock in m1, m2: - self.assertNotCallable(m1) + self.assertNotCallable(mock) def test_new_callable_keyword_arguments(self): From 9e3f49cd664d32f9241aaf2ae96e527ae95312a3 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Wed, 18 Dec 2024 11:35:29 +0530 Subject: [PATCH 379/388] gh-127949: deprecate `asyncio.set_event_loop_policy` (#128024) First step towards deprecating the asyncio policy system. This deprecates `asyncio.set_event_loop_policy` and will be removed in Python 3.16. Backports: 5892853fb71acd6530e1e241a9a4bcf71a61fb21 Signed-off-by: Chris Withers --- mock/tests/testasync.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index f21b9fa8..8a78efe4 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -25,7 +25,7 @@ def run(main): def tearDownModule(): - asyncio.set_event_loop_policy(None) + asyncio._set_event_loop_policy(None) class AsyncClass: From 45d2d494c645cdd6cbe005591ab5cc28fc33dd58 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 14 Jan 2025 10:02:38 +0200 Subject: [PATCH 380/388] gh-71339: Add additional assertion methods for unittest (GH-128707) Add the following methods: * assertHasAttr() and assertNotHasAttr() * assertIsSubclass() and assertNotIsSubclass() * assertStartsWith() and assertNotStartsWith() * assertEndsWith() and assertNotEndsWith() Also improve error messages for assertIsInstance() and assertNotIsInstance(). Backports: 06cad77a5b345adde88609be9c3c470c5cd9f417 Signed-off-by: Chris Withers --- ...2025-01-10-15-06-45.gh-issue-71339.EKnpzw.rst | 9 +++++++++ mock/tests/testasync.py | 14 +++++++------- mock/tests/testcallable.py | 14 +++++++------- mock/tests/testhelpers.py | 2 +- mock/tests/testmagicmethods.py | 12 ++++++------ mock/tests/testmock.py | 16 ++++++++-------- mock/tests/testpatch.py | 6 +++--- 7 files changed, 41 insertions(+), 32 deletions(-) create mode 100644 NEWS.d/2025-01-10-15-06-45.gh-issue-71339.EKnpzw.rst diff --git a/NEWS.d/2025-01-10-15-06-45.gh-issue-71339.EKnpzw.rst b/NEWS.d/2025-01-10-15-06-45.gh-issue-71339.EKnpzw.rst new file mode 100644 index 00000000..5f33a30b --- /dev/null +++ b/NEWS.d/2025-01-10-15-06-45.gh-issue-71339.EKnpzw.rst @@ -0,0 +1,9 @@ +Add new assertion methods for :mod:`unittest`: +:meth:`~unittest.TestCase.assertHasAttr`, +:meth:`~unittest.TestCase.assertNotHasAttr`, +:meth:`~unittest.TestCase.assertIsSubclass`, +:meth:`~unittest.TestCase.assertNotIsSubclass` +:meth:`~unittest.TestCase.assertStartsWith`, +:meth:`~unittest.TestCase.assertNotStartsWith`, +:meth:`~unittest.TestCase.assertEndsWith` and +:meth:`~unittest.TestCase.assertNotEndsWith`. diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index 8a78efe4..b3e07b4e 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -600,16 +600,16 @@ def test_sync_magic_methods_return_magic_mocks(self): 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__")) + self.assertHasAttr(m_mock, "__aenter__") + self.assertHasAttr(m_mock, "__aexit__") + self.assertHasAttr(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__")) + self.assertHasAttr(a_mock, "__enter__") + self.assertHasAttr(a_mock, "__exit__") + self.assertHasAttr(a_mock, "__next__") + self.assertHasAttr(a_mock, "__len__") def test_magic_methods_are_async_functions(self): m_mock = MagicMock() diff --git a/mock/tests/testcallable.py b/mock/tests/testcallable.py index 41715ed1..8153d0a3 100644 --- a/mock/tests/testcallable.py +++ b/mock/tests/testcallable.py @@ -22,21 +22,21 @@ def assertNotCallable(self, mock): def test_non_callable(self): for mock in NonCallableMagicMock(), NonCallableMock(): self.assertRaises(TypeError, mock) - self.assertFalse(hasattr(mock, '__call__')) + self.assertNotHasAttr(mock, '__call__') self.assertIn(mock.__class__.__name__, repr(mock)) def test_hierarchy(self): - self.assertTrue(issubclass(MagicMock, Mock)) - self.assertTrue(issubclass(NonCallableMagicMock, NonCallableMock)) + self.assertIsSubclass(MagicMock, Mock) + self.assertIsSubclass(NonCallableMagicMock, NonCallableMock) def test_attributes(self): one = NonCallableMock() - self.assertTrue(issubclass(type(one.one), Mock)) + self.assertIsSubclass(type(one.one), Mock) two = NonCallableMagicMock() - self.assertTrue(issubclass(type(two.two), MagicMock)) + self.assertIsSubclass(type(two.two), MagicMock) def test_subclasses(self): @@ -44,13 +44,13 @@ class MockSub(Mock): pass one = MockSub() - self.assertTrue(issubclass(type(one.one), MockSub)) + self.assertIsSubclass(type(one.one), MockSub) class MagicSub(MagicMock): pass two = MagicSub() - self.assertTrue(issubclass(type(two.two), MagicSub)) + self.assertIsSubclass(type(two.two), MagicSub) def test_patch_spec(self): diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index be57bfb0..6ccc6e51 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -965,7 +965,7 @@ def __getattr__(self, attribute): proxy = Foo() autospec = create_autospec(proxy) - self.assertFalse(hasattr(autospec, '__name__')) + self.assertNotHasAttr(autospec, '__name__') def test_autospec_signature_staticmethod(self): diff --git a/mock/tests/testmagicmethods.py b/mock/tests/testmagicmethods.py index 7e242f4a..f980a95a 100644 --- a/mock/tests/testmagicmethods.py +++ b/mock/tests/testmagicmethods.py @@ -10,13 +10,13 @@ class TestMockingMagicMethods(unittest.TestCase): def test_deleting_magic_methods(self): mock = Mock() - self.assertFalse(hasattr(mock, '__getitem__')) + self.assertNotHasAttr(mock, '__getitem__') mock.__getitem__ = Mock() - self.assertTrue(hasattr(mock, '__getitem__')) + self.assertHasAttr(mock, '__getitem__') del mock.__getitem__ - self.assertFalse(hasattr(mock, '__getitem__')) + self.assertNotHasAttr(mock, '__getitem__') def test_magicmock_del(self): @@ -252,12 +252,12 @@ def test_magicmock(self): self.assertEqual(list(mock), [1, 2, 3]) getattr(mock, '__bool__').return_value = False - self.assertFalse(hasattr(mock, '__nonzero__')) + self.assertNotHasAttr(mock, '__nonzero__') self.assertFalse(bool(mock)) for entry in _magics: - self.assertTrue(hasattr(mock, entry)) - self.assertFalse(hasattr(mock, '__imaginary__')) + self.assertHasAttr(mock, entry) + self.assertNotHasAttr(mock, '__imaginary__') def test_magic_mock_equality(self): diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index daf82b88..5053df51 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -2225,13 +2225,13 @@ def test_attach_mock_patch_autospec_signature(self): def test_attribute_deletion(self): for mock in (Mock(), MagicMock(), NonCallableMagicMock(), NonCallableMock()): - self.assertTrue(hasattr(mock, 'm')) + self.assertHasAttr(mock, 'm') del mock.m - self.assertFalse(hasattr(mock, 'm')) + self.assertNotHasAttr(mock, 'm') del mock.f - self.assertFalse(hasattr(mock, 'f')) + self.assertNotHasAttr(mock, 'f') self.assertRaises(AttributeError, getattr, mock, 'f') @@ -2240,18 +2240,18 @@ def test_mock_does_not_raise_on_repeated_attribute_deletion(self): for mock in (Mock(), MagicMock(), NonCallableMagicMock(), NonCallableMock()): mock.foo = 3 - self.assertTrue(hasattr(mock, 'foo')) + self.assertHasAttr(mock, 'foo') self.assertEqual(mock.foo, 3) del mock.foo - self.assertFalse(hasattr(mock, 'foo')) + self.assertNotHasAttr(mock, 'foo') mock.foo = 4 - self.assertTrue(hasattr(mock, 'foo')) + self.assertHasAttr(mock, 'foo') self.assertEqual(mock.foo, 4) del mock.foo - self.assertFalse(hasattr(mock, 'foo')) + self.assertNotHasAttr(mock, 'foo') def test_mock_raises_when_deleting_nonexistent_attribute(self): @@ -2269,7 +2269,7 @@ def test_reset_mock_does_not_raise_on_attr_deletion(self): mock.child = True del mock.child mock.reset_mock() - self.assertFalse(hasattr(mock, 'child')) + self.assertNotHasAttr(mock, 'child') def test_class_assignable(self): diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index a8924022..e3fbbefe 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -364,7 +364,7 @@ def test(): self.assertEqual(SomeClass.frooble, sentinel.Frooble) test() - self.assertFalse(hasattr(SomeClass, 'frooble')) + self.assertNotHasAttr(SomeClass, 'frooble') def test_patch_wont_create_by_default(self): @@ -381,7 +381,7 @@ def test_patchobject_wont_create_by_default(self): @patch.object(SomeClass, 'ord', sentinel.Frooble) def test(): pass test() - self.assertFalse(hasattr(SomeClass, 'ord')) + self.assertNotHasAttr(SomeClass, 'ord') def test_patch_builtins_without_create(self): @@ -1475,7 +1475,7 @@ def test_patch_multiple_create(self): finally: patcher.stop() - self.assertFalse(hasattr(Foo, 'blam')) + self.assertNotHasAttr(Foo, 'blam') def test_patch_multiple_spec_set(self): From 4221c21ff6a373e4bdf566e014f234b7d67dd946 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 3 Mar 2025 10:32:19 +0000 Subject: [PATCH 381/388] Revert "gh-71339: Add additional assertion methods for unittest (GH-128707)" This reverts commit 7cf5bc4c33caff7bf808634611001da6b6896333. Older python versions don't have these new assert methods --- ...2025-01-10-15-06-45.gh-issue-71339.EKnpzw.rst | 9 --------- lastsync.txt | 2 +- mock/tests/testasync.py | 14 +++++++------- mock/tests/testcallable.py | 14 +++++++------- mock/tests/testhelpers.py | 2 +- mock/tests/testmagicmethods.py | 12 ++++++------ mock/tests/testmock.py | 16 ++++++++-------- mock/tests/testpatch.py | 6 +++--- 8 files changed, 33 insertions(+), 42 deletions(-) delete mode 100644 NEWS.d/2025-01-10-15-06-45.gh-issue-71339.EKnpzw.rst diff --git a/NEWS.d/2025-01-10-15-06-45.gh-issue-71339.EKnpzw.rst b/NEWS.d/2025-01-10-15-06-45.gh-issue-71339.EKnpzw.rst deleted file mode 100644 index 5f33a30b..00000000 --- a/NEWS.d/2025-01-10-15-06-45.gh-issue-71339.EKnpzw.rst +++ /dev/null @@ -1,9 +0,0 @@ -Add new assertion methods for :mod:`unittest`: -:meth:`~unittest.TestCase.assertHasAttr`, -:meth:`~unittest.TestCase.assertNotHasAttr`, -:meth:`~unittest.TestCase.assertIsSubclass`, -:meth:`~unittest.TestCase.assertNotIsSubclass` -:meth:`~unittest.TestCase.assertStartsWith`, -:meth:`~unittest.TestCase.assertNotStartsWith`, -:meth:`~unittest.TestCase.assertEndsWith` and -:meth:`~unittest.TestCase.assertNotEndsWith`. diff --git a/lastsync.txt b/lastsync.txt index 1730b693..df85a1e1 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -e6d5ff55d0816d7f5eb45e49c810e936b09d2be7 +5892853fb71acd6530e1e241a9a4bcf71a61fb21 diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index b3e07b4e..8a78efe4 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -600,16 +600,16 @@ def test_sync_magic_methods_return_magic_mocks(self): def test_magicmock_has_async_magic_methods(self): m_mock = MagicMock() - self.assertHasAttr(m_mock, "__aenter__") - self.assertHasAttr(m_mock, "__aexit__") - self.assertHasAttr(m_mock, "__anext__") + 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.assertHasAttr(a_mock, "__enter__") - self.assertHasAttr(a_mock, "__exit__") - self.assertHasAttr(a_mock, "__next__") - self.assertHasAttr(a_mock, "__len__") + 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() diff --git a/mock/tests/testcallable.py b/mock/tests/testcallable.py index 8153d0a3..41715ed1 100644 --- a/mock/tests/testcallable.py +++ b/mock/tests/testcallable.py @@ -22,21 +22,21 @@ def assertNotCallable(self, mock): def test_non_callable(self): for mock in NonCallableMagicMock(), NonCallableMock(): self.assertRaises(TypeError, mock) - self.assertNotHasAttr(mock, '__call__') + self.assertFalse(hasattr(mock, '__call__')) self.assertIn(mock.__class__.__name__, repr(mock)) def test_hierarchy(self): - self.assertIsSubclass(MagicMock, Mock) - self.assertIsSubclass(NonCallableMagicMock, NonCallableMock) + self.assertTrue(issubclass(MagicMock, Mock)) + self.assertTrue(issubclass(NonCallableMagicMock, NonCallableMock)) def test_attributes(self): one = NonCallableMock() - self.assertIsSubclass(type(one.one), Mock) + self.assertTrue(issubclass(type(one.one), Mock)) two = NonCallableMagicMock() - self.assertIsSubclass(type(two.two), MagicMock) + self.assertTrue(issubclass(type(two.two), MagicMock)) def test_subclasses(self): @@ -44,13 +44,13 @@ class MockSub(Mock): pass one = MockSub() - self.assertIsSubclass(type(one.one), MockSub) + self.assertTrue(issubclass(type(one.one), MockSub)) class MagicSub(MagicMock): pass two = MagicSub() - self.assertIsSubclass(type(two.two), MagicSub) + self.assertTrue(issubclass(type(two.two), MagicSub)) def test_patch_spec(self): diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index 6ccc6e51..be57bfb0 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -965,7 +965,7 @@ def __getattr__(self, attribute): proxy = Foo() autospec = create_autospec(proxy) - self.assertNotHasAttr(autospec, '__name__') + self.assertFalse(hasattr(autospec, '__name__')) def test_autospec_signature_staticmethod(self): diff --git a/mock/tests/testmagicmethods.py b/mock/tests/testmagicmethods.py index f980a95a..7e242f4a 100644 --- a/mock/tests/testmagicmethods.py +++ b/mock/tests/testmagicmethods.py @@ -10,13 +10,13 @@ class TestMockingMagicMethods(unittest.TestCase): def test_deleting_magic_methods(self): mock = Mock() - self.assertNotHasAttr(mock, '__getitem__') + self.assertFalse(hasattr(mock, '__getitem__')) mock.__getitem__ = Mock() - self.assertHasAttr(mock, '__getitem__') + self.assertTrue(hasattr(mock, '__getitem__')) del mock.__getitem__ - self.assertNotHasAttr(mock, '__getitem__') + self.assertFalse(hasattr(mock, '__getitem__')) def test_magicmock_del(self): @@ -252,12 +252,12 @@ def test_magicmock(self): self.assertEqual(list(mock), [1, 2, 3]) getattr(mock, '__bool__').return_value = False - self.assertNotHasAttr(mock, '__nonzero__') + self.assertFalse(hasattr(mock, '__nonzero__')) self.assertFalse(bool(mock)) for entry in _magics: - self.assertHasAttr(mock, entry) - self.assertNotHasAttr(mock, '__imaginary__') + self.assertTrue(hasattr(mock, entry)) + self.assertFalse(hasattr(mock, '__imaginary__')) def test_magic_mock_equality(self): diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index 5053df51..daf82b88 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -2225,13 +2225,13 @@ def test_attach_mock_patch_autospec_signature(self): def test_attribute_deletion(self): for mock in (Mock(), MagicMock(), NonCallableMagicMock(), NonCallableMock()): - self.assertHasAttr(mock, 'm') + self.assertTrue(hasattr(mock, 'm')) del mock.m - self.assertNotHasAttr(mock, 'm') + self.assertFalse(hasattr(mock, 'm')) del mock.f - self.assertNotHasAttr(mock, 'f') + self.assertFalse(hasattr(mock, 'f')) self.assertRaises(AttributeError, getattr, mock, 'f') @@ -2240,18 +2240,18 @@ def test_mock_does_not_raise_on_repeated_attribute_deletion(self): for mock in (Mock(), MagicMock(), NonCallableMagicMock(), NonCallableMock()): mock.foo = 3 - self.assertHasAttr(mock, 'foo') + self.assertTrue(hasattr(mock, 'foo')) self.assertEqual(mock.foo, 3) del mock.foo - self.assertNotHasAttr(mock, 'foo') + self.assertFalse(hasattr(mock, 'foo')) mock.foo = 4 - self.assertHasAttr(mock, 'foo') + self.assertTrue(hasattr(mock, 'foo')) self.assertEqual(mock.foo, 4) del mock.foo - self.assertNotHasAttr(mock, 'foo') + self.assertFalse(hasattr(mock, 'foo')) def test_mock_raises_when_deleting_nonexistent_attribute(self): @@ -2269,7 +2269,7 @@ def test_reset_mock_does_not_raise_on_attr_deletion(self): mock.child = True del mock.child mock.reset_mock() - self.assertNotHasAttr(mock, 'child') + self.assertFalse(hasattr(mock, 'child')) def test_class_assignable(self): diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index e3fbbefe..a8924022 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -364,7 +364,7 @@ def test(): self.assertEqual(SomeClass.frooble, sentinel.Frooble) test() - self.assertNotHasAttr(SomeClass, 'frooble') + self.assertFalse(hasattr(SomeClass, 'frooble')) def test_patch_wont_create_by_default(self): @@ -381,7 +381,7 @@ def test_patchobject_wont_create_by_default(self): @patch.object(SomeClass, 'ord', sentinel.Frooble) def test(): pass test() - self.assertNotHasAttr(SomeClass, 'ord') + self.assertFalse(hasattr(SomeClass, 'ord')) def test_patch_builtins_without_create(self): @@ -1475,7 +1475,7 @@ def test_patch_multiple_create(self): finally: patcher.stop() - self.assertNotHasAttr(Foo, 'blam') + self.assertFalse(hasattr(Foo, 'blam')) def test_patch_multiple_spec_set(self): From fdacaa2b68e3bb06851bfb991ecd8ed063b49f3e Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 3 Mar 2025 10:36:36 +0000 Subject: [PATCH 382/388] Add set_event_loop_policy to backports Since it's been deprecated in newer Python versions --- mock/backports.py | 6 ++++++ mock/tests/testasync.py | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/mock/backports.py b/mock/backports.py index c268a3c2..598a512b 100644 --- a/mock/backports.py +++ b/mock/backports.py @@ -90,3 +90,9 @@ def run(self, result=None): return super().run(result) finally: self._tearDownAsyncioLoop() + + +try: + from asyncio import _set_event_loop_policy as set_event_loop_policy +except ImportError: + from asyncio import set_event_loop_policy diff --git a/mock/tests/testasync.py b/mock/tests/testasync.py index 8a78efe4..5bf01392 100644 --- a/mock/tests/testasync.py +++ b/mock/tests/testasync.py @@ -8,7 +8,7 @@ from mock import (ANY, call, AsyncMock, patch, MagicMock, Mock, create_autospec, sentinel, seal) -from mock.backports import IsolatedAsyncioTestCase, iscoroutinefunction +from mock.backports import IsolatedAsyncioTestCase, iscoroutinefunction, set_event_loop_policy from mock.mock import _CallList @@ -25,7 +25,7 @@ def run(main): def tearDownModule(): - asyncio._set_event_loop_policy(None) + set_event_loop_policy(None) class AsyncClass: From 9081bbe8183c83f84309abd61dc879c2d9510abc Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 3 Mar 2025 11:04:58 +0000 Subject: [PATCH 383/388] Fix bug in backporting docs --- docs/index.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.txt b/docs/index.txt index a74a26c7..0c671ebf 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -116,7 +116,7 @@ Backporting rules .. code-block:: python def will_never_be_called(): - pass # pragma: no cov + pass # pragma: no cover - If code such as this causes coverage checking to drop below 100%: From aee44ede42c4cb4d529701352445806e730e62f8 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 3 Mar 2025 10:44:42 +0000 Subject: [PATCH 384/388] Backport-specific code coverage fixes --- mock/mock.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mock/mock.py b/mock/mock.py index 37ee85aa..236fcac7 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -2310,7 +2310,7 @@ def __get__(self, obj, _type=None): try: _CODE_SIG = inspect.signature(partial(CodeType.__init__, None)) _CODE_ATTRS = dir(CodeType) -except ValueError: +except ValueError: # pragma: no cover - backport is only tested against builds with docstrings _CODE_SIG = None @@ -2335,7 +2335,7 @@ def __init__(self, *args, **kwargs): code_mock = NonCallableMock(spec_set=_CODE_ATTRS) code_mock.__dict__["_spec_class"] = CodeType code_mock.__dict__["_spec_signature"] = _CODE_SIG - else: + else: # pragma: no cover - backport is only tested against builds with docstrings code_mock = NonCallableMock(spec_set=CodeType) code_mock.co_flags = ( inspect.CO_COROUTINE From 4fc238eb1c612a148e198e743bba0b200896417c Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 3 Mar 2025 11:44:59 +0000 Subject: [PATCH 385/388] `unittest.mock` test and coverage fixup (#130787) * Mark functions that will never be called with # pragma: no cover * Fix testpatch.PatchTest.test_exit_idempotent .stop() and __exit__ have subtly different code paths, so to really test __exit__ idempotency, we need to call it specifically twice. Backports: 04091c083340dde7d4eeb6d945c70f3b37d88f85 Signed-off-by: Chris Withers --- mock/tests/testhelpers.py | 2 +- mock/tests/testmock.py | 2 +- mock/tests/testpatch.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mock/tests/testhelpers.py b/mock/tests/testhelpers.py index be57bfb0..2dc1bf62 100644 --- a/mock/tests/testhelpers.py +++ b/mock/tests/testhelpers.py @@ -1097,7 +1097,7 @@ def test_dataclass_with_method(self): class WithMethod: a: int def b(self) -> int: - return 1 + return 1 # pragma: no cover for mock in [ create_autospec(WithMethod, instance=True), diff --git a/mock/tests/testmock.py b/mock/tests/testmock.py index daf82b88..44a40c62 100644 --- a/mock/tests/testmock.py +++ b/mock/tests/testmock.py @@ -317,7 +317,7 @@ def test_explicit_return_value_even_if_mock_wraps_object(self): passed to the wrapped object and the return_value is returned instead. """ def my_func(): - return None + return None # pragma: no cover func_mock = create_autospec(spec=my_func, wraps=my_func) return_value = "explicit return value" func_mock.return_value = return_value diff --git a/mock/tests/testpatch.py b/mock/tests/testpatch.py index a8924022..193b6ce7 100644 --- a/mock/tests/testpatch.py +++ b/mock/tests/testpatch.py @@ -746,7 +746,7 @@ def test_stop_idempotent(self): def test_exit_idempotent(self): patcher = patch(foo_name, 'bar', 3) with patcher: - patcher.stop() + patcher.__exit__(None, None, None) def test_second_start_failure(self): From 563f3115652a4d889e93a4751d1e04dc20dd5d0a Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 3 Mar 2025 10:35:59 +0000 Subject: [PATCH 386/388] latest sync point --- lastsync.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lastsync.txt b/lastsync.txt index df85a1e1..ba0f9346 100644 --- a/lastsync.txt +++ b/lastsync.txt @@ -1 +1 @@ -5892853fb71acd6530e1e241a9a4bcf71a61fb21 +04091c083340dde7d4eeb6d945c70f3b37d88f85 From 3c737e2454dad995d0397e0d05c8a7a3a41f484d Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 3 Mar 2025 12:19:35 +0000 Subject: [PATCH 387/388] Preparing for 5.2.0 release. --- CHANGELOG.rst | 40 +++++++++++++++++++ ...-12-22-20-49-52.gh-issue-113407.C_O13_.rst | 1 - ...-12-29-17-57-45.gh-issue-113569.qcRCEI.rst | 2 - ...4-02-27-13-05-51.gh-issue-75988.In6LlB.rst | 1 - ...4-04-22-21-54-12.gh-issue-90848.5jHEEc.rst | 1 - ...4-06-04-08-57-02.gh-issue-65454.o9j4wF.rst | 1 - ...-06-10-14-00-40.gh-issue-119600.jJMf4C.rst | 2 - ...-06-19-15-06-58.gh-issue-120732.OvYV9b.rst | 2 - ...-07-14-12-25-53.gh-issue-117765.YFMOUv.rst | 1 - ...-08-10-10-21-44.gh-issue-122858.ZC1rJD.rst | 2 - ...-09-13-10-34-19.gh-issue-123934.yMe7mL.rst | 2 - ...-09-24-13-32-16.gh-issue-124176.6hmOPz.rst | 4 -- ...-11-10-18-14-51.gh-issue-104745.zAa5Ke.rst | 3 -- mock/__init__.py | 2 +- 14 files changed, 41 insertions(+), 23 deletions(-) delete mode 100644 NEWS.d/2023-12-22-20-49-52.gh-issue-113407.C_O13_.rst delete mode 100644 NEWS.d/2023-12-29-17-57-45.gh-issue-113569.qcRCEI.rst delete mode 100644 NEWS.d/2024-02-27-13-05-51.gh-issue-75988.In6LlB.rst delete mode 100644 NEWS.d/2024-04-22-21-54-12.gh-issue-90848.5jHEEc.rst delete mode 100644 NEWS.d/2024-06-04-08-57-02.gh-issue-65454.o9j4wF.rst delete mode 100644 NEWS.d/2024-06-10-14-00-40.gh-issue-119600.jJMf4C.rst delete mode 100644 NEWS.d/2024-06-19-15-06-58.gh-issue-120732.OvYV9b.rst delete mode 100644 NEWS.d/2024-07-14-12-25-53.gh-issue-117765.YFMOUv.rst delete mode 100644 NEWS.d/2024-08-10-10-21-44.gh-issue-122858.ZC1rJD.rst delete mode 100644 NEWS.d/2024-09-13-10-34-19.gh-issue-123934.yMe7mL.rst delete mode 100644 NEWS.d/2024-09-24-13-32-16.gh-issue-124176.6hmOPz.rst delete mode 100644 NEWS.d/2024-11-10-18-14-51.gh-issue-104745.zAa5Ke.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6e8b6973..21cd287a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,43 @@ +5.2.0 +----- + +- gh-65454: :func:`unittest.mock.Mock.attach_mock` no longer triggers a call + to a ``PropertyMock`` being attached. + +- gh-117765: Improved documentation for :func:`unittest.mock.patch.dict` + +- gh-124176: Add support for :func:`dataclasses.dataclass` in + :func:`unittest.mock.create_autospec`. Now ``create_autospec`` will check + for potential dataclasses and use :func:`dataclasses.fields` function to + retrieve the spec information. + +- gh-123934: Fix :class:`unittest.mock.MagicMock` reseting magic methods + return values after ``.reset_mock(return_value=True)`` was called. + +- gh-90848: Fixed :func:`unittest.mock.create_autospec` to configure parent + mock with keyword arguments. + +- gh-113569: Indicate if there were no actual calls in unittest + :meth:`~unittest.mock.Mock.assert_has_calls` failure. + +- gh-122858: Deprecate :func:`!asyncio.iscoroutinefunction` in favor of + :func:`inspect.iscoroutinefunction`. + +- gh-104745: Limit starting a patcher (from :func:`unittest.mock.patch` or + :func:`unittest.mock.patch.object`) more than once without stopping it + +- gh-75988: Fixed :func:`unittest.mock.create_autospec` to pass the call + through to the wrapped object to return the real result. + +- gh-119600: Fix :func:`unittest.mock.patch` to not read attributes of the + target when ``new_callable`` is set. Patch by Robert Collins. + +- gh-113407: Fix import of :mod:`unittest.mock` when CPython is built + without docstrings. + +- gh-120732: Fix ``name`` passing to :class:`unittest.mock.Mock` object when + using :func:`unittest.mock.create_autospec`. + 5.1.0 ----- diff --git a/NEWS.d/2023-12-22-20-49-52.gh-issue-113407.C_O13_.rst b/NEWS.d/2023-12-22-20-49-52.gh-issue-113407.C_O13_.rst deleted file mode 100644 index da00977f..00000000 --- a/NEWS.d/2023-12-22-20-49-52.gh-issue-113407.C_O13_.rst +++ /dev/null @@ -1 +0,0 @@ -Fix import of :mod:`unittest.mock` when CPython is built without docstrings. diff --git a/NEWS.d/2023-12-29-17-57-45.gh-issue-113569.qcRCEI.rst b/NEWS.d/2023-12-29-17-57-45.gh-issue-113569.qcRCEI.rst deleted file mode 100644 index 9b63fc94..00000000 --- a/NEWS.d/2023-12-29-17-57-45.gh-issue-113569.qcRCEI.rst +++ /dev/null @@ -1,2 +0,0 @@ -Indicate if there were no actual calls in unittest -:meth:`~unittest.mock.Mock.assert_has_calls` failure. diff --git a/NEWS.d/2024-02-27-13-05-51.gh-issue-75988.In6LlB.rst b/NEWS.d/2024-02-27-13-05-51.gh-issue-75988.In6LlB.rst deleted file mode 100644 index 682b7cfa..00000000 --- a/NEWS.d/2024-02-27-13-05-51.gh-issue-75988.In6LlB.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed :func:`unittest.mock.create_autospec` to pass the call through to the wrapped object to return the real result. diff --git a/NEWS.d/2024-04-22-21-54-12.gh-issue-90848.5jHEEc.rst b/NEWS.d/2024-04-22-21-54-12.gh-issue-90848.5jHEEc.rst deleted file mode 100644 index adbca012..00000000 --- a/NEWS.d/2024-04-22-21-54-12.gh-issue-90848.5jHEEc.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed :func:`unittest.mock.create_autospec` to configure parent mock with keyword arguments. diff --git a/NEWS.d/2024-06-04-08-57-02.gh-issue-65454.o9j4wF.rst b/NEWS.d/2024-06-04-08-57-02.gh-issue-65454.o9j4wF.rst deleted file mode 100644 index 0b232cf8..00000000 --- a/NEWS.d/2024-06-04-08-57-02.gh-issue-65454.o9j4wF.rst +++ /dev/null @@ -1 +0,0 @@ -:func:`unittest.mock.Mock.attach_mock` no longer triggers a call to a ``PropertyMock`` being attached. diff --git a/NEWS.d/2024-06-10-14-00-40.gh-issue-119600.jJMf4C.rst b/NEWS.d/2024-06-10-14-00-40.gh-issue-119600.jJMf4C.rst deleted file mode 100644 index 04c9ca9c..00000000 --- a/NEWS.d/2024-06-10-14-00-40.gh-issue-119600.jJMf4C.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix :func:`unittest.mock.patch` to not read attributes of the target when -``new_callable`` is set. Patch by Robert Collins. diff --git a/NEWS.d/2024-06-19-15-06-58.gh-issue-120732.OvYV9b.rst b/NEWS.d/2024-06-19-15-06-58.gh-issue-120732.OvYV9b.rst deleted file mode 100644 index e31c4dd3..00000000 --- a/NEWS.d/2024-06-19-15-06-58.gh-issue-120732.OvYV9b.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix ``name`` passing to :class:`unittest.mock.Mock` object when using -:func:`unittest.mock.create_autospec`. diff --git a/NEWS.d/2024-07-14-12-25-53.gh-issue-117765.YFMOUv.rst b/NEWS.d/2024-07-14-12-25-53.gh-issue-117765.YFMOUv.rst deleted file mode 100644 index a727c1aa..00000000 --- a/NEWS.d/2024-07-14-12-25-53.gh-issue-117765.YFMOUv.rst +++ /dev/null @@ -1 +0,0 @@ -Improved documentation for :func:`unittest.mock.patch.dict` diff --git a/NEWS.d/2024-08-10-10-21-44.gh-issue-122858.ZC1rJD.rst b/NEWS.d/2024-08-10-10-21-44.gh-issue-122858.ZC1rJD.rst deleted file mode 100644 index d452ad6a..00000000 --- a/NEWS.d/2024-08-10-10-21-44.gh-issue-122858.ZC1rJD.rst +++ /dev/null @@ -1,2 +0,0 @@ -Deprecate :func:`!asyncio.iscoroutinefunction` in favor of -:func:`inspect.iscoroutinefunction`. diff --git a/NEWS.d/2024-09-13-10-34-19.gh-issue-123934.yMe7mL.rst b/NEWS.d/2024-09-13-10-34-19.gh-issue-123934.yMe7mL.rst deleted file mode 100644 index cec7741b..00000000 --- a/NEWS.d/2024-09-13-10-34-19.gh-issue-123934.yMe7mL.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix :class:`unittest.mock.MagicMock` reseting magic methods return values -after ``.reset_mock(return_value=True)`` was called. diff --git a/NEWS.d/2024-09-24-13-32-16.gh-issue-124176.6hmOPz.rst b/NEWS.d/2024-09-24-13-32-16.gh-issue-124176.6hmOPz.rst deleted file mode 100644 index 38c03066..00000000 --- a/NEWS.d/2024-09-24-13-32-16.gh-issue-124176.6hmOPz.rst +++ /dev/null @@ -1,4 +0,0 @@ -Add support for :func:`dataclasses.dataclass` in -:func:`unittest.mock.create_autospec`. Now ``create_autospec`` will check -for potential dataclasses and use :func:`dataclasses.fields` function to -retrieve the spec information. diff --git a/NEWS.d/2024-11-10-18-14-51.gh-issue-104745.zAa5Ke.rst b/NEWS.d/2024-11-10-18-14-51.gh-issue-104745.zAa5Ke.rst deleted file mode 100644 index c83a1076..00000000 --- a/NEWS.d/2024-11-10-18-14-51.gh-issue-104745.zAa5Ke.rst +++ /dev/null @@ -1,3 +0,0 @@ -Limit starting a patcher (from :func:`unittest.mock.patch` or -:func:`unittest.mock.patch.object`) more than -once without stopping it diff --git a/mock/__init__.py b/mock/__init__.py index 9c4e2d01..055601d8 100644 --- a/mock/__init__.py +++ b/mock/__init__.py @@ -7,7 +7,7 @@ import mock.mock as _mock from mock.mock import * -__version__ = '5.1.0' +__version__ = '5.2.0' version_info = tuple(int(p) for p in re.match(r'(\d+).(\d+).(\d+)', __version__).groups()) From 0f5df822bde4729cb2190819ad01a8728ddc9de3 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 3 Mar 2025 12:25:49 +0000 Subject: [PATCH 388/388] In memoriam: Michael Foord 1974-2025 --- CHANGELOG.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 21cd287a..cd1b360c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,11 @@ 5.2.0 ----- +This is release is `in memory of Michael Foord`__, who originally authored the `mock` package +and passed away in January 2025. + +__ https://discuss.python.org/t/in-memoriam-michael-foord-1974-2025/78317 + - gh-65454: :func:`unittest.mock.Mock.attach_mock` no longer triggers a call to a ``PropertyMock`` being attached.