summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorNikita Sobolev <mail@sobolevn.me>2021-09-14 10:20:40 (GMT)
committerGitHub <noreply@github.com>2021-09-14 10:20:40 (GMT)
commit7f60c9e1c6e22cc0e846a872c318570926cd3094 (patch)
treec1b19fca9b51e98473e63e0bf43767086011c283 /Lib
parentc99fc4e53a60084df88ac5c69b3b13bc033677e1 (diff)
downloadcpython-7f60c9e1c6e22cc0e846a872c318570926cd3094.zip
cpython-7f60c9e1c6e22cc0e846a872c318570926cd3094.tar.gz
cpython-7f60c9e1c6e22cc0e846a872c318570926cd3094.tar.bz2
bpo-45156: Fixes inifite loop on unittest.mock.seal() (GH-28300)
Fixes infinite loop on unittest.mock.seal() of mocks created by unittest.create_autospec(). Co-authored-by: Dong-hee Na <donghee.na92@gmail.com>
Diffstat (limited to 'Lib')
-rw-r--r--Lib/unittest/mock.py13
-rw-r--r--Lib/unittest/test/testmock/testsealable.py61
2 files changed, 68 insertions, 6 deletions
diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py
index 8193efc..9f99a5a 100644
--- a/Lib/unittest/mock.py
+++ b/Lib/unittest/mock.py
@@ -1005,6 +1005,11 @@ class NonCallableMock(Base):
if _new_name in self.__dict__['_spec_asyncs']:
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)
if issubclass(_type, MagicMock) and _new_name in _async_method_magics:
# Any asynchronous magic becomes an AsyncMock
@@ -1023,12 +1028,6 @@ class NonCallableMock(Base):
klass = Mock
else:
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)
@@ -2913,6 +2912,8 @@ def seal(mock):
continue
if not isinstance(m, NonCallableMock):
continue
+ if isinstance(m._mock_children.get(attr), _SpecState):
+ continue
if m._mock_new_parent is mock:
seal(m)
diff --git a/Lib/unittest/test/testmock/testsealable.py b/Lib/unittest/test/testmock/testsealable.py
index 59f5233..11784c3 100644
--- a/Lib/unittest/test/testmock/testsealable.py
+++ b/Lib/unittest/test/testmock/testsealable.py
@@ -171,6 +171,67 @@ class TestSealable(unittest.TestCase):
m.test1().test2.test3().test4()
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__":
unittest.main()