1
0
mirror of https://github.com/certbot/certbot.git synced 2026-01-26 07:41:33 +03:00

#4071 Mixin to prevent setting return_value after initializing certain Mock objects (#4963)

* Addressing #4071 Wrote an ImmutableReturnMixin to prevent developers overriding return_value in certain Mock objects

* Language

* Loosening the assumption that underlying _mock objects need to be Immutable-like simplifies implementation

* Addressing #4071

* Ensure side_effects and return_values are pushed down to the underlying _mock in FreezableMocks. And IDisplay mocks are no longer frozen in _create_get_utility_mock()

* Edit a handful of tests to not override the mock_get_utility return_value

* Brief explainer of FreezableMock.__setattr__

* Incorporating PR feedback and some compatibility

* FreezableMock __getattr__ needs a shortcut in case of return_value or side_effect

* Changing return_value only forbidden if set before freezing

* Remove unnecessary else block

* Expanded doc strings

* Bring a couple new tests in line with patch_get_utility() norms
This commit is contained in:
Chris Julian
2017-08-30 12:52:45 -04:00
committed by Brad Warren
parent ae0be73b53
commit 2bfc92e58d
6 changed files with 55 additions and 33 deletions

View File

@@ -121,7 +121,8 @@ class MultipleVhostsTest(util.ApacheTest):
@certbot_util.patch_get_utility()
def test_get_all_names(self, mock_getutility):
mock_getutility.notification = mock.MagicMock(return_value=True)
mock_utility = mock_getutility()
mock_utility.notification = mock.MagicMock(return_value=True)
names = self.config.get_all_names()
self.assertEqual(names, set(
["certbot.demo", "ocspvhost.com", "encryption-example.demo"]
@@ -131,9 +132,8 @@ class MultipleVhostsTest(util.ApacheTest):
@mock.patch("certbot_apache.configurator.socket.gethostbyaddr")
def test_get_all_names_addrs(self, mock_gethost, mock_getutility):
mock_gethost.side_effect = [("google.com", "", ""), socket.error]
notification = mock.Mock()
notification.notification = mock.Mock(return_value=True)
mock_getutility.return_value = notification
mock_utility = mock_getutility()
mock_utility.notification.return_value = True
vhost = obj.VirtualHost(
"fp", "ap",
set([obj.Addr(("8.8.8.8", "443")),

View File

@@ -158,10 +158,11 @@ class AuthenticatorTest(unittest.TestCase):
@test_util.patch_get_utility()
def test_perform_eaddrinuse_retry(self, mock_get_utility):
mock_utility = mock_get_utility()
errno = socket.errno.EADDRINUSE
error = errors.StandaloneBindError(mock.MagicMock(errno=errno), -1)
self.auth.servers.run.side_effect = [error] + 2 * [mock.MagicMock()]
mock_yesno = mock_get_utility.return_value.yesno
mock_yesno = mock_utility.yesno
mock_yesno.return_value = True
self.test_perform()
@@ -169,7 +170,8 @@ class AuthenticatorTest(unittest.TestCase):
@test_util.patch_get_utility()
def test_perform_eaddrinuse_no_retry(self, mock_get_utility):
mock_yesno = mock_get_utility.return_value.yesno
mock_utility = mock_get_utility()
mock_yesno = mock_utility.yesno
mock_yesno.return_value = False
errno = socket.errno.EADDRINUSE

View File

@@ -346,9 +346,8 @@ class RenameLineageTest(BaseCertManagerTest):
self.assertRaises(errors.Error, self._call, self.config)
mock_renewal_conf_files.return_value = ["one.conf"]
util_mock = mock.Mock()
util_mock = mock_get_utility()
util_mock.menu.return_value = (display_util.CANCEL, 0)
mock_get_utility.return_value = util_mock
self.assertRaises(errors.Error, self._call, self.config)
util_mock.menu.return_value = (display_util.OK, -1)
@@ -359,14 +358,11 @@ class RenameLineageTest(BaseCertManagerTest):
self.config.certname = "one"
self.config.new_certname = None
util_mock = mock.Mock()
util_mock = mock_get_utility()
util_mock.input.return_value = (display_util.CANCEL, "name")
mock_get_utility.return_value = util_mock
self.assertRaises(errors.Error, self._call, self.config)
util_mock = mock.Mock()
util_mock.input.return_value = (display_util.OK, None)
mock_get_utility.return_value = util_mock
self.assertRaises(errors.Error, self._call, self.config)
@test_util.patch_get_utility()
@@ -393,9 +389,8 @@ class RenameLineageTest(BaseCertManagerTest):
def test_rename_cert_interactive_certname(self, mock_check, mock_get_utility):
mock_check.return_value = True
self.config.certname = None
util_mock = mock.Mock()
util_mock = mock_get_utility()
util_mock.menu.return_value = (display_util.OK, 0)
mock_get_utility.return_value = util_mock
self._call(self.config)
from certbot import cert_manager
updated_lineage = cert_manager.lineage_for_certname(self.config, self.config.new_certname)

View File

@@ -310,10 +310,11 @@ class ChooseNamesTest(unittest.TestCase):
@test_util.patch_get_utility("certbot.display.ops.z_util")
def test_choose_manually(self, mock_util):
from certbot.display.ops import _choose_names_manually
utility_mock = mock_util()
# No retry
mock_util().yesno.return_value = False
utility_mock.yesno.return_value = False
# IDN and no retry
mock_util().input.return_value = (display_util.OK,
utility_mock.input.return_value = (display_util.OK,
"uniçodé.com")
self.assertEqual(_choose_names_manually(), [])
# IDN exception with previous mocks
@@ -324,7 +325,7 @@ class ChooseNamesTest(unittest.TestCase):
mock_sli.side_effect = unicode_error
self.assertEqual(_choose_names_manually(), [])
# Valid domains
mock_util().input.return_value = (display_util.OK,
utility_mock.input.return_value = (display_util.OK,
("example.com,"
"under_score.example.com,"
"justtld,"
@@ -332,14 +333,17 @@ class ChooseNamesTest(unittest.TestCase):
self.assertEqual(_choose_names_manually(),
["example.com", "under_score.example.com",
"justtld", "valid.example.com"])
@test_util.patch_get_utility("certbot.display.ops.z_util")
def test_choose_manually_retry(self, mock_util):
from certbot.display.ops import _choose_names_manually
utility_mock = mock_util()
# Three iterations
mock_util().input.return_value = (display_util.OK,
utility_mock.input.return_value = (display_util.OK,
"uniçodé.com")
yn = mock.MagicMock()
yn.side_effect = [True, True, False]
mock_util().yesno = yn
utility_mock.yesno.side_effect = [True, True, False]
_choose_names_manually()
self.assertEqual(mock_util().yesno.call_count, 3)
self.assertEqual(utility_mock.yesno.call_count, 3)
class SuccessInstallationTest(unittest.TestCase):

View File

@@ -164,9 +164,7 @@ class CertonlyTest(unittest.TestCase):
self.assertTrue(mock_report_cert.call_count == 2)
# error in _ask_user_to_confirm_new_names
util_mock = mock.Mock()
util_mock.yesno.return_value = False
self.mock_get_utility.return_value = util_mock
self.mock_get_utility().yesno.return_value = False
self.assertRaises(errors.ConfigurationError, self._call,
('certonly --webroot -d example.com -d test.com --cert-name example.com').split())
@@ -1115,7 +1113,7 @@ class UnregisterTest(unittest.TestCase):
def test_abort_unregister(self):
self.mocks['account'].AccountFileStorage.return_value = mock.Mock()
util_mock = self.mocks['get_utility'].return_value
util_mock = self.mocks['get_utility']()
util_mock.yesno.return_value = False
config = mock.Mock()

View File

@@ -173,8 +173,8 @@ class FreezableMock(object):
"""Mock object with the ability to freeze attributes.
This class works like a regular mock.MagicMock object, except
attributes and behavior can be set and frozen so they cannot be
changed during tests.
attributes and behavior set before the object is frozen cannot
be changed during tests.
If a func argument is provided to the constructor, this function
is called first when an instance of FreezableMock is called,
@@ -182,10 +182,12 @@ class FreezableMock(object):
value of func is ignored.
"""
def __init__(self, frozen=False, func=None):
def __init__(self, frozen=False, func=None, return_value=mock.sentinel.DEFAULT):
self._frozen_set = set() if frozen else set(('freeze',))
self._func = func
self._mock = mock.MagicMock()
if return_value != mock.sentinel.DEFAULT:
self.return_value = return_value
self._frozen = frozen
def freeze(self):
@@ -203,17 +205,38 @@ class FreezableMock(object):
return object.__getattribute__(self, name)
except AttributeError:
return False
elif name in ('return_value', 'side_effect',):
return getattr(object.__getattribute__(self, '_mock'), name)
elif name == '_frozen_set' or name in self._frozen_set:
return object.__getattribute__(self, name)
else:
return getattr(object.__getattribute__(self, '_mock'), name)
def __setattr__(self, name, value):
""" Before it is frozen, attributes are set on the FreezableMock
instance and added to the _frozen_set. Attributes in the _frozen_set
cannot be changed after the FreezableMock is frozen. In this case,
they are set on the underlying _mock.
In cases of return_value and side_effect, these attributes are always
passed through to the instance's _mock and added to the _frozen_set
before the object is frozen.
"""
if self._frozen:
return setattr(self._mock, name, value)
elif name != '_frozen_set':
if name in self._frozen_set:
raise AttributeError('Cannot change frozen attribute ' + name)
else:
return setattr(self._mock, name, value)
if name != '_frozen_set':
self._frozen_set.add(name)
return object.__setattr__(self, name, value)
if name in ('return_value', 'side_effect'):
return setattr(self._mock, name, value)
else:
return object.__setattr__(self, name, value)
def _create_get_utility_mock():
@@ -223,7 +246,7 @@ def _create_get_utility_mock():
frozen_mock = FreezableMock(frozen=True, func=_assert_valid_call)
setattr(display, name, frozen_mock)
display.freeze()
return mock.MagicMock(return_value=display)
return FreezableMock(frozen=True, return_value=display)
def _assert_valid_call(*args, **kwargs):