mirror of https://github.com/python/cpython.git
bpo-45156: Fixes inifite loop on unittest.mock.seal() (GH-28300) (GH-28326)
Fixes infinite loop on unittest.mock.seal() of mocks created by
unittest.create_autospec().
Co-authored-by: Dong-hee Na <donghee.na92@gmail.com>
(cherry picked from commit 7f60c9e1c6
)
Co-authored-by: Nikita Sobolev <mail@sobolevn.me>
This commit is contained in:
parent
a390bb6d66
commit
fd74d2680e
|
@ -1004,6 +1004,11 @@ def _get_child_mock(self, /, **kw):
|
||||||
if _new_name in self.__dict__['_spec_asyncs']:
|
if _new_name in self.__dict__['_spec_asyncs']:
|
||||||
return AsyncMock(**kw)
|
return AsyncMock(**kw)
|
||||||
|
|
||||||
|
if self._mock_sealed:
|
||||||
|
attribute = f".{kw['name']}" if "name" in kw else "()"
|
||||||
|
mock_name = self._extract_mock_name() + attribute
|
||||||
|
raise AttributeError(mock_name)
|
||||||
|
|
||||||
_type = type(self)
|
_type = type(self)
|
||||||
if issubclass(_type, MagicMock) and _new_name in _async_method_magics:
|
if issubclass(_type, MagicMock) and _new_name in _async_method_magics:
|
||||||
# Any asynchronous magic becomes an AsyncMock
|
# Any asynchronous magic becomes an AsyncMock
|
||||||
|
@ -1022,12 +1027,6 @@ def _get_child_mock(self, /, **kw):
|
||||||
klass = Mock
|
klass = Mock
|
||||||
else:
|
else:
|
||||||
klass = _type.__mro__[1]
|
klass = _type.__mro__[1]
|
||||||
|
|
||||||
if self._mock_sealed:
|
|
||||||
attribute = "." + kw["name"] if "name" in kw else "()"
|
|
||||||
mock_name = self._extract_mock_name() + attribute
|
|
||||||
raise AttributeError(mock_name)
|
|
||||||
|
|
||||||
return klass(**kw)
|
return klass(**kw)
|
||||||
|
|
||||||
|
|
||||||
|
@ -2927,6 +2926,8 @@ def seal(mock):
|
||||||
continue
|
continue
|
||||||
if not isinstance(m, NonCallableMock):
|
if not isinstance(m, NonCallableMock):
|
||||||
continue
|
continue
|
||||||
|
if isinstance(m._mock_children.get(attr), _SpecState):
|
||||||
|
continue
|
||||||
if m._mock_new_parent is mock:
|
if m._mock_new_parent is mock:
|
||||||
seal(m)
|
seal(m)
|
||||||
|
|
||||||
|
|
|
@ -171,6 +171,67 @@ def test_call_chain_is_maintained(self):
|
||||||
m.test1().test2.test3().test4()
|
m.test1().test2.test3().test4()
|
||||||
self.assertIn("mock.test1().test2.test3().test4", str(cm.exception))
|
self.assertIn("mock.test1().test2.test3().test4", str(cm.exception))
|
||||||
|
|
||||||
|
def test_seal_with_autospec(self):
|
||||||
|
# https://bugs.python.org/issue45156
|
||||||
|
class Foo:
|
||||||
|
foo = 0
|
||||||
|
def bar1(self):
|
||||||
|
return 1
|
||||||
|
def bar2(self):
|
||||||
|
return 2
|
||||||
|
|
||||||
|
class Baz:
|
||||||
|
baz = 3
|
||||||
|
def ban(self):
|
||||||
|
return 4
|
||||||
|
|
||||||
|
for spec_set in (True, False):
|
||||||
|
with self.subTest(spec_set=spec_set):
|
||||||
|
foo = mock.create_autospec(Foo, spec_set=spec_set)
|
||||||
|
foo.bar1.return_value = 'a'
|
||||||
|
foo.Baz.ban.return_value = 'b'
|
||||||
|
|
||||||
|
mock.seal(foo)
|
||||||
|
|
||||||
|
self.assertIsInstance(foo.foo, mock.NonCallableMagicMock)
|
||||||
|
self.assertIsInstance(foo.bar1, mock.MagicMock)
|
||||||
|
self.assertIsInstance(foo.bar2, mock.MagicMock)
|
||||||
|
self.assertIsInstance(foo.Baz, mock.MagicMock)
|
||||||
|
self.assertIsInstance(foo.Baz.baz, mock.NonCallableMagicMock)
|
||||||
|
self.assertIsInstance(foo.Baz.ban, mock.MagicMock)
|
||||||
|
|
||||||
|
self.assertEqual(foo.bar1(), 'a')
|
||||||
|
foo.bar1.return_value = 'new_a'
|
||||||
|
self.assertEqual(foo.bar1(), 'new_a')
|
||||||
|
self.assertEqual(foo.Baz.ban(), 'b')
|
||||||
|
foo.Baz.ban.return_value = 'new_b'
|
||||||
|
self.assertEqual(foo.Baz.ban(), 'new_b')
|
||||||
|
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
foo.foo()
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
foo.bar = 1
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
foo.bar2()
|
||||||
|
|
||||||
|
foo.bar2.return_value = 'bar2'
|
||||||
|
self.assertEqual(foo.bar2(), 'bar2')
|
||||||
|
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
foo.missing_attr
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
foo.missing_attr = 1
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
foo.missing_method()
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
foo.Baz.baz()
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
foo.Baz.missing_attr
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
foo.Baz.missing_attr = 1
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
foo.Baz.missing_method()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Fixes infinite loop on :func:`unittest.mock.seal` of mocks created by
|
||||||
|
:func:`~unittest.create_autospec`.
|
Loading…
Reference in New Issue