diff options
author | Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> | 2022-10-28 10:16:53 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-10-28 10:16:53 (GMT) |
commit | 3f0f74387b8b4fa8dc4def7c2cace67c0331b532 (patch) | |
tree | 103e1f0d85c0bba70d29a585734ae579633c5088 /Lib/unittest | |
parent | af204e4c47babdefb307b3f25eb152732c73a533 (diff) | |
download | cpython-3f0f74387b8b4fa8dc4def7c2cace67c0331b532.zip cpython-3f0f74387b8b4fa8dc4def7c2cace67c0331b532.tar.gz cpython-3f0f74387b8b4fa8dc4def7c2cace67c0331b532.tar.bz2 |
[3.10] gh-98624 Add mutex to unittest.mock.NonCallableMock (GH-98688) (#98798)
(cherry picked from commit 0346eddbe933b5f1f56151bdebf5bd49392bc275)
Co-authored-by: noah-weingarden <33741795+noah-weingarden@users.noreply.github.com>
Diffstat (limited to 'Lib/unittest')
-rw-r--r-- | Lib/unittest/mock.py | 66 |
1 files changed, 38 insertions, 28 deletions
diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 3f7185b..b49030a 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -34,6 +34,7 @@ from asyncio import iscoroutinefunction from types import CodeType, ModuleType, MethodType from unittest.util import safe_repr from functools import wraps, partial +from threading import RLock class InvalidSpecError(Exception): @@ -401,6 +402,14 @@ class Base(object): class NonCallableMock(Base): """A non-callable version of `Mock`""" + # Store a mutex as a class attribute in order to protect concurrent access + # to mock attributes. Using a class attribute allows all NonCallableMock + # instances to share the mutex for simplicity. + # + # See https://github.com/python/cpython/issues/98624 for why this is + # necessary. + _lock = RLock() + def __new__(cls, /, *args, **kw): # every instance has its own class # so we can create magic methods on the @@ -640,35 +649,36 @@ class NonCallableMock(Base): f"{name!r} is not a valid assertion. Use a spec " f"for the mock if {name!r} is meant to be an attribute.") - result = self._mock_children.get(name) - if result is _deleted: - raise AttributeError(name) - elif result is None: - wraps = None - if self._mock_wraps is not None: - # XXXX should we get the attribute without triggering code - # execution? - wraps = getattr(self._mock_wraps, name) - - result = self._get_child_mock( - parent=self, name=name, wraps=wraps, _new_name=name, - _new_parent=self - ) - self._mock_children[name] = result - - elif isinstance(result, _SpecState): - try: - result = create_autospec( - result.spec, result.spec_set, result.instance, - result.parent, result.name + with NonCallableMock._lock: + result = self._mock_children.get(name) + if result is _deleted: + raise AttributeError(name) + elif result is None: + wraps = None + if self._mock_wraps is not None: + # XXXX should we get the attribute without triggering code + # execution? + wraps = getattr(self._mock_wraps, name) + + result = self._get_child_mock( + parent=self, name=name, wraps=wraps, _new_name=name, + _new_parent=self ) - except InvalidSpecError: - target_name = self.__dict__['_mock_name'] or self - raise InvalidSpecError( - f'Cannot autospec attr {name!r} from target ' - f'{target_name!r} as it has already been mocked out. ' - f'[target={self!r}, attr={result.spec!r}]') - self._mock_children[name] = result + self._mock_children[name] = result + + elif isinstance(result, _SpecState): + try: + result = create_autospec( + result.spec, result.spec_set, result.instance, + result.parent, result.name + ) + except InvalidSpecError: + target_name = self.__dict__['_mock_name'] or self + raise InvalidSpecError( + f'Cannot autospec attr {name!r} from target ' + f'{target_name!r} as it has already been mocked out. ' + f'[target={self!r}, attr={result.spec!r}]') + self._mock_children[name] = result return result |