Check version requirements on optional dependencies (#3618)

* Add and test activate function to acme.

This function can be used to check if our optional dependencies are
available and they meet our version requirements.

* use activate in dns_resolver

* use activate in dns_available() in challenges_test

* Use activate in dns_resolver_test

* Use activate in certbot.plugins.util_test

* Use acme.util.activate for psutil

* Better testing and handling of missing deps

* Factored out *_available() code into a common function

* Delayed exception caused from using acme.dns_resolver without
  dnspython until the function is called. This makes both
  production and testing code simpler.

* Make a common subclass for already_listening tests

* Simplify mocking of USE_PSUTIL in tests
This commit is contained in:
Brad Warren
2016-10-12 15:55:50 -07:00
committed by Brad Warren
parent 20ac4aebaf
commit 052be6d4ba
10 changed files with 157 additions and 101 deletions
+8 -2
View File
@@ -5,13 +5,19 @@ import socket
import zope.component
from acme import errors as acme_errors
from acme import util as acme_util
from certbot import interfaces
from certbot import util
PSUTIL_REQUIREMENT = "psutil>=2.2.1"
try:
import psutil
acme_util.activate(PSUTIL_REQUIREMENT)
import psutil # pragma: no cover
USE_PSUTIL = True
except ImportError:
except acme_errors.DependencyError: # pragma: no cover
USE_PSUTIL = False
logger = logging.getLogger(__name__)
+27 -51
View File
@@ -1,11 +1,11 @@
"""Tests for certbot.plugins.util."""
import os
import socket
import unittest
import sys
import mock
from six.moves import reload_module # pylint: disable=import-error
from certbot.plugins.util import PSUTIL_REQUIREMENT
from certbot.tests import test_util
@@ -34,71 +34,47 @@ class PathSurgeryTest(unittest.TestCase):
self.assertTrue("/tmp" in os.environ["PATH"])
class AlreadyListeningTestNoPsutil(unittest.TestCase):
class AlreadyListeningTest(unittest.TestCase):
"""Tests for certbot.plugins.already_listening."""
@classmethod
def _call(cls, *args, **kwargs):
from certbot.plugins.util import already_listening
return already_listening(*args, **kwargs)
class AlreadyListeningTestNoPsutil(AlreadyListeningTest):
"""Tests for certbot.plugins.already_listening when
psutil is not available"""
def setUp(self):
import certbot.plugins.util
# Ensure we get importerror
self.psutil = None
if "psutil" in sys.modules:
self.psutil = sys.modules['psutil']
sys.modules['psutil'] = None
# Reload hackery to ensure getting non-psutil version
# loaded to memory
reload_module(certbot.plugins.util)
def tearDown(self):
# Need to reload the module to ensure
# getting back to normal
import certbot.plugins.util
sys.modules["psutil"] = self.psutil
reload_module(certbot.plugins.util)
@classmethod
def _call(cls, *args, **kwargs):
with mock.patch("certbot.plugins.util.USE_PSUTIL", False):
return super(
AlreadyListeningTestNoPsutil, cls)._call(*args, **kwargs)
@mock.patch("certbot.plugins.util.zope.component.getUtility")
def test_ports_available(self, mock_getutil):
import certbot.plugins.util as plugins_util
# Ensure we don't get error
with mock.patch("socket.socket.bind"):
self.assertFalse(plugins_util.already_listening(80))
self.assertFalse(plugins_util.already_listening(80, True))
self.assertFalse(self._call(80))
self.assertFalse(self._call(80, True))
self.assertEqual(mock_getutil.call_count, 0)
@mock.patch("certbot.plugins.util.zope.component.getUtility")
def test_ports_blocked(self, mock_getutil):
sys.modules["psutil"] = None
import certbot.plugins.util as plugins_util
import socket
with mock.patch("socket.socket.bind", side_effect=socket.error):
self.assertTrue(plugins_util.already_listening(80))
self.assertTrue(plugins_util.already_listening(80, True))
with mock.patch("socket.socket", side_effect=socket.error):
self.assertFalse(plugins_util.already_listening(80))
with mock.patch("certbot.plugins.util.socket.socket.bind") as mock_bind:
mock_bind.side_effect = socket.error
self.assertTrue(self._call(80))
self.assertTrue(self._call(80, True))
with mock.patch("certbot.plugins.util.socket.socket") as mock_socket:
mock_socket.side_effect = socket.error
self.assertFalse(self._call(80))
self.assertEqual(mock_getutil.call_count, 2)
def psutil_available():
"""Checks if psutil can be imported.
:rtype: bool
:returns: ``True`` if psutil can be imported, otherwise, ``False``
"""
try:
import psutil # pylint: disable=unused-variable
except ImportError:
return False
return True
@test_util.skip_unless(psutil_available(),
@test_util.skip_unless(test_util.requirement_available(PSUTIL_REQUIREMENT),
"optional dependency psutil is not available")
class AlreadyListeningTestPsutil(unittest.TestCase):
class AlreadyListeningTestPsutil(AlreadyListeningTest):
"""Tests for certbot.plugins.already_listening."""
def _call(self, *args, **kwargs):
from certbot.plugins.util import already_listening
return already_listening(*args, **kwargs)
@mock.patch("certbot.plugins.util.psutil.net_connections")
@mock.patch("certbot.plugins.util.psutil.Process")
@mock.patch("certbot.plugins.util.zope.component.getUtility")