diff --git a/certbot/main.py b/certbot/main.py index 9d33d6059..07b13fbad 100644 --- a/certbot/main.py +++ b/certbot/main.py @@ -1,5 +1,6 @@ """Certbot main entry point.""" from __future__ import print_function +import functools import logging.handlers import os import sys @@ -492,7 +493,7 @@ def install(config, plugins): _install_cert(config, le_client, domains) -def plugins_cmd(config, plugins): # TODO: Use IDisplay rather than print +def plugins_cmd(config, plugins): """List server software plugins.""" logger.debug("Expected interfaces: %s", config.ifaces) @@ -500,8 +501,10 @@ def plugins_cmd(config, plugins): # TODO: Use IDisplay rather than print filtered = plugins.visible().ifaces(ifaces) logger.debug("Filtered plugins: %r", filtered) + notify = functools.partial(zope.component.getUtility( + interfaces.IDisplay).notification, pause=False) if not config.init and not config.prepare: - print(str(filtered)) + notify(str(filtered)) return filtered.init(config) @@ -509,13 +512,13 @@ def plugins_cmd(config, plugins): # TODO: Use IDisplay rather than print logger.debug("Verified plugins: %r", verified) if not config.prepare: - print(str(verified)) + notify(str(verified)) return verified.prepare() available = verified.available() logger.debug("Prepared plugins: %s", available) - print(str(available)) + notify(str(available)) def rollback(config, plugins): diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py index 4b9e4cf67..15128b717 100644 --- a/certbot/tests/main_test.py +++ b/certbot/tests/main_test.py @@ -539,7 +539,10 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met ifaces = [] plugins = mock_disco.PluginsRegistry.find_all() - _, stdout, _, _ = self._call(['plugins']) + stdout = six.StringIO() + with test_util.patch_get_utility_with_stdout(stdout=stdout): + _, stdout, _, _ = self._call(['plugins'], stdout) + plugins.visible.assert_called_once_with() plugins.visible().ifaces.assert_called_once_with(ifaces) filtered = plugins.visible().ifaces() @@ -556,14 +559,16 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met _, _, _, _ = directory, mode, uid, strict raise errors.Error() + stdout = six.StringIO() with mock.patch('certbot.util.set_up_core_dir') as mock_set_up_core_dir: - mock_set_up_core_dir.side_effect = throw_error + with test_util.patch_get_utility_with_stdout(stdout=stdout): + mock_set_up_core_dir.side_effect = throw_error + _, stdout, _, _ = self._call(['plugins'], stdout) - _, stdout, _, _ = self._call(['plugins']) - plugins.visible.assert_called_once_with() - plugins.visible().ifaces.assert_called_once_with(ifaces) - filtered = plugins.visible().ifaces() - self.assertEqual(stdout.getvalue().strip(), str(filtered)) + plugins.visible.assert_called_once_with() + plugins.visible().ifaces.assert_called_once_with(ifaces) + filtered = plugins.visible().ifaces() + self.assertEqual(stdout.getvalue().strip(), str(filtered)) @mock.patch('certbot.main.plugins_disco') @mock.patch('certbot.main.cli.HelpfulArgumentParser.determine_help_topics') @@ -571,7 +576,10 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met ifaces = [] plugins = mock_disco.PluginsRegistry.find_all() - _, stdout, _, _ = self._call(['plugins', '--init']) + stdout = six.StringIO() + with test_util.patch_get_utility_with_stdout(stdout=stdout): + _, stdout, _, _ = self._call(['plugins', '--init'], stdout) + plugins.visible.assert_called_once_with() plugins.visible().ifaces.assert_called_once_with(ifaces) filtered = plugins.visible().ifaces() @@ -585,7 +593,11 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met def test_plugins_prepare(self, _det, mock_disco): ifaces = [] plugins = mock_disco.PluginsRegistry.find_all() - _, stdout, _, _ = self._call(['plugins', '--init', '--prepare']) + + stdout = six.StringIO() + with test_util.patch_get_utility_with_stdout(stdout=stdout): + _, stdout, _, _ = self._call(['plugins', '--init', '--prepare'], stdout) + plugins.visible.assert_called_once_with() plugins.visible().ifaces.assert_called_once_with(ifaces) filtered = plugins.visible().ifaces() diff --git a/certbot/tests/util.py b/certbot/tests/util.py index 73d002989..c43b44522 100644 --- a/certbot/tests/util.py +++ b/certbot/tests/util.py @@ -14,6 +14,7 @@ from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization import mock import OpenSSL +import six from six.moves import reload_module # pylint: disable=import-error from acme import jose @@ -169,6 +170,30 @@ def patch_get_utility(target='zope.component.getUtility'): return mock.patch(target, new_callable=_create_get_utility_mock) +def patch_get_utility_with_stdout(target='zope.component.getUtility', + stdout=None): + """Patch zope.component.getUtility to use a special mock IDisplay. + + The mock IDisplay works like a regular mock object, except it also + also asserts that methods are called with valid arguments. + + The `message` argument passed to the IDisplay methods is passed to + stdout's write method. + + :param str target: path to patch + :param object stdout: object to write standard output to; it is + expected to have a `write` method + + :returns: mock zope.component.getUtility + :rtype: mock.MagicMock + + """ + stdout = stdout if stdout else six.StringIO() + + freezable_mock = _create_get_utility_mock_with_stdout(stdout) + return mock.patch(target, new=freezable_mock) + + class FreezableMock(object): """Mock object with the ability to freeze attributes. @@ -249,6 +274,36 @@ def _create_get_utility_mock(): return FreezableMock(frozen=True, return_value=display) +def _create_get_utility_mock_with_stdout(stdout): + def _write_msg(message, *unused_args, **unused_kwargs): + """Write to message to stdout. + """ + if message: + stdout.write(message) + + def mock_method(*args, **kwargs): + """ + Mock function for IDisplay methods. + """ + _assert_valid_call(args, kwargs) + _write_msg(*args, **kwargs) + + + display = FreezableMock() + for name in interfaces.IDisplay.names(): # pylint: disable=no-member + if name == 'notification': + frozen_mock = FreezableMock(frozen=True, + func=_write_msg) + setattr(display, name, frozen_mock) + else: + frozen_mock = FreezableMock(frozen=True, + func=mock_method) + setattr(display, name, frozen_mock) + display.freeze() + + return FreezableMock(frozen=True, return_value=display) + + def _assert_valid_call(*args, **kwargs): assert_args = [args[0] if args else kwargs['message']]