diff options
author | Nikita Sobolev <mail@sobolevn.me> | 2021-09-14 10:20:40 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-14 10:20:40 (GMT) |
commit | 7f60c9e1c6e22cc0e846a872c318570926cd3094 (patch) | |
tree | c1b19fca9b51e98473e63e0bf43767086011c283 /Lib | |
parent | c99fc4e53a60084df88ac5c69b3b13bc033677e1 (diff) | |
download | cpython-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.py | 13 | ||||
-rw-r--r-- | Lib/unittest/test/testmock/testsealable.py | 61 |
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() |