From b9578835c493f040dc09586194d6045531293998 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sat, 1 Mar 2025 10:39:59 +0000 Subject: [PATCH 01/38] 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 c702930df3fc47453428e4a82ababfff056b97a5 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sat, 1 Mar 2025 10:40:11 +0000 Subject: [PATCH 02/38] 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 9942de9d6556c0f336e495895797d3e6e9a4ef67 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sat, 1 Mar 2025 10:42:49 +0000 Subject: [PATCH 03/38] 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 cc3ca4c6c1e9043e5578db6676b8d3cc95a40593 Mon Sep 17 00:00:00 2001 From: Mario Corchero Date: Mon, 17 Jul 2023 20:57:40 +0200 Subject: [PATCH 04/38] 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 342f25375a29e0343805ea11a6074b7734fa691f Mon Sep 17 00:00:00 2001 From: Sangyun_LEE Date: Mon, 4 Sep 2023 06:19:49 +0900 Subject: [PATCH 05/38] 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 33a6d72443989d137523d54f280cf18465082497 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Sun, 24 Sep 2023 15:07:23 +0100 Subject: [PATCH 06/38] 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 236785fe51eaab74011c85d37069847f32e91804 Mon Sep 17 00:00:00 2001 From: James Date: Wed, 18 Oct 2023 03:36:16 -0400 Subject: [PATCH 07/38] 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 c9fda3172a6fccdd8f2c4e4e30b989905d77b1da Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 24 Dec 2023 13:38:56 +0200 Subject: [PATCH 08/38] 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 88cf9eff6d78a49c12073cfb9758eddc643acaf4 Mon Sep 17 00:00:00 2001 From: wookie184 Date: Thu, 4 Jan 2024 19:11:34 +0000 Subject: [PATCH 09/38] 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 86235ef33a4fd8b27cb5e765bc85618f7cf11004 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sat, 1 Mar 2025 18:11:00 +0000 Subject: [PATCH 10/38] 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 531064f90bd952bc30c89f4be7afe7aebe3c09f2 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Sun, 11 Feb 2024 11:51:25 +0300 Subject: [PATCH 11/38] 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 d6bc819db3d976453bf02983e4c52482f51c3ab4 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 12/38] 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 6e3aca15a9f68e3ff6df73138894ecb1db626c0a Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 30 Apr 2024 17:23:44 +0300 Subject: [PATCH 13/38] 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 e5fd961d199bf13a038bd69a7bb2f3644926b53f Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Sat, 1 Mar 2025 18:02:32 +0000 Subject: [PATCH 14/38] 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 d8fad1798f74a2a5aa0ac0c159920ea5957a27c1 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 15/38] 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 b994ccb9b2df9c69dac93731da0102f615250ac6 Mon Sep 17 00:00:00 2001 From: Geoffrey Thomas Date: Wed, 22 May 2024 12:35:18 -0400 Subject: [PATCH 16/38] 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 091f8d581c0ce759dc743d3569a9b599e1802124 Mon Sep 17 00:00:00 2001 From: Robert Collins Date: Tue, 11 Jun 2024 07:41:12 +0200 Subject: [PATCH 17/38] 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 f31ce73d45893fc391548ef68c2900a1ed0264d4 Mon Sep 17 00:00:00 2001 From: blhsing Date: Tue, 11 Jun 2024 13:42:49 +0800 Subject: [PATCH 18/38] 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 d1b6956e0d712c4d0a10e08d74c695766f99a63d Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Wed, 19 Jun 2024 23:35:11 +0300 Subject: [PATCH 19/38] 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 0243cb6bbb70e6f437d0f5bc0444ca67ac964336 Mon Sep 17 00:00:00 2001 From: Dominic H Date: Mon, 15 Jul 2024 09:14:17 +0200 Subject: [PATCH 20/38] 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 74d0fcccde394610e7f954996518586f567c503d Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 3 Mar 2025 10:01:42 +0000 Subject: [PATCH 21/38] 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 7abb784272c869d4d5b29ff0b4e6e524e575e529 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 3 Mar 2025 10:05:15 +0000 Subject: [PATCH 22/38] 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 c0920f3fee584b17d16f36df0871f281a40dfe84 Mon Sep 17 00:00:00 2001 From: Wulian <1055917385@qq.com> Date: Mon, 12 Aug 2024 00:35:51 +0800 Subject: [PATCH 23/38] 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 5ae648f92f474ef3e353c6b42cf8307f431da5c9 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 3 Mar 2025 10:16:27 +0000 Subject: [PATCH 24/38] 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 ce4de2cb3a3f54e22062faa60191cc4bd0ae2b85 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 3 Mar 2025 10:20:28 +0000 Subject: [PATCH 25/38] 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 2982e18c789712af108aa6e523ac5d738325aeaa Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 14 Sep 2024 13:20:44 +0300 Subject: [PATCH 26/38] 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 82afa5f8755786ee5c37dc421387f99d48292058 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 19 Sep 2024 10:55:47 +0300 Subject: [PATCH 27/38] 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 e4aa029e8f44c364328ab1b205a0a28305e07036 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 26 Sep 2024 15:06:52 +0300 Subject: [PATCH 28/38] 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 dd8d0c281d5995975818c6d0d5bb4b6436eae7e9 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 27 Sep 2024 09:48:31 +0300 Subject: [PATCH 29/38] 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 22d17c3a5c022d54959b2d67b4da174af4b3c71c 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 30/38] 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 8ac6d62ebab844d1a2ef3469530540d4e4c3966e Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Wed, 18 Dec 2024 11:35:29 +0530 Subject: [PATCH 31/38] 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 7cf5bc4c33caff7bf808634611001da6b6896333 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 14 Jan 2025 10:02:38 +0200 Subject: [PATCH 32/38] 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 5f2a77231dc6998bc111296f7ed0a6098ceec8b8 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 3 Mar 2025 10:32:19 +0000 Subject: [PATCH 33/38] 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 f3afe3b0822addc111412583fdf0421c31cb7625 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 3 Mar 2025 10:36:36 +0000 Subject: [PATCH 34/38] 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 13edc928bdfbbd9f2d3979fb25d09ddc4b0d8932 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 3 Mar 2025 11:04:58 +0000 Subject: [PATCH 35/38] 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 012c5ac0eaa7489474ce04009e0e2855ebe69b3d Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 3 Mar 2025 10:44:42 +0000 Subject: [PATCH 36/38] 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 b4356fba04fad5f2c5008395981d5b2e09796852 Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 3 Mar 2025 11:44:59 +0000 Subject: [PATCH 37/38] `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 3a9e80913005b084ec2a0db1a1239aa5e79d000c Mon Sep 17 00:00:00 2001 From: Chris Withers Date: Mon, 3 Mar 2025 10:35:59 +0000 Subject: [PATCH 38/38] 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