diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 9f99a5aa5bcdcb..ff5ae392ccf6da 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -494,6 +494,10 @@ def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False, _spec_asyncs = [] for attr in dir(spec): + if isinstance(inspect.getattr_static(spec, attr, None), property): + # Don't execute `property` decorators with `getattr`. + # It might affect user's code in unknown way. + continue if iscoroutinefunction(getattr(spec, attr, None)): _spec_asyncs.append(attr) diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py index fdba543b53511d..d76d83f1717773 100644 --- a/Lib/unittest/test/testmock/testmock.py +++ b/Lib/unittest/test/testmock/testmock.py @@ -896,6 +896,36 @@ def set_attr(): self.assertRaises(AttributeError, set_attr) + def test_class_with_property(self): + class X: + @property + def some(self): + raise ValueError('Should not be raised') + + mock = Mock(spec=X) + self.assertIsInstance(mock, X) + + mock = Mock(spec=X()) + self.assertIsInstance(mock, X) + + + def test_class_with_settable_property(self): + class X: + @property + def some(self): + raise ValueError('Should not be raised') + + @some.setter + def some(self, value): + raise TypeError('Should not be raised') + + mock = Mock(spec=X) + self.assertIsInstance(mock, X) + + mock = Mock(spec=X()) + self.assertIsInstance(mock, X) + + def test_copy(self): current = sys.getrecursionlimit() self.addCleanup(sys.setrecursionlimit, current) diff --git a/Misc/NEWS.d/next/Library/2021-12-03-11-19-44.bpo-45756.nSDJWj.rst b/Misc/NEWS.d/next/Library/2021-12-03-11-19-44.bpo-45756.nSDJWj.rst new file mode 100644 index 00000000000000..36e2ca4b56c150 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-12-03-11-19-44.bpo-45756.nSDJWj.rst @@ -0,0 +1,2 @@ +Do not execute ``@property`` descriptors while creating autospecs in :mod:`unittest.mock`. +This was not safe and could affect users' code in unknown way.