summaryrefslogtreecommitdiffstats
path: root/Lib/unittest
diff options
context:
space:
mode:
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2022-10-28 10:16:53 (GMT)
committerGitHub <noreply@github.com>2022-10-28 10:16:53 (GMT)
commit3f0f74387b8b4fa8dc4def7c2cace67c0331b532 (patch)
tree103e1f0d85c0bba70d29a585734ae579633c5088 /Lib/unittest
parentaf204e4c47babdefb307b3f25eb152732c73a533 (diff)
downloadcpython-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.py66
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