summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDoug Hellmann <doug.hellmann@gmail.com>2013-11-25 07:45:09 -0800
committerDoug Hellmann <doug.hellmann@gmail.com>2013-11-25 07:45:09 -0800
commit3e5c24f83aa4085b791bce0f336553f1a6029b5e (patch)
treef586c066e7796fca0e82edaf09d3098f73be50dd
parentcc4e5c18dc5bc9d39adc2107342002c7239e14ae (diff)
parente2d86a8a0901e30db3697e6a71bc191a8cec4cbd (diff)
downloadstevedore-3e5c24f83aa4085b791bce0f336553f1a6029b5e.tar.gz
Merge pull request #29 from drocco-007/master
Test instance factories
-rw-r--r--stevedore/dispatch.py3
-rw-r--r--stevedore/driver.py44
-rw-r--r--stevedore/enabled.py2
-rw-r--r--stevedore/extension.py53
-rw-r--r--stevedore/hook.py8
-rw-r--r--stevedore/named.py60
-rw-r--r--stevedore/tests/test_test_manager.py278
7 files changed, 425 insertions, 23 deletions
diff --git a/stevedore/dispatch.py b/stevedore/dispatch.py
index 6536ee9..2ff455d 100644
--- a/stevedore/dispatch.py
+++ b/stevedore/dispatch.py
@@ -142,6 +142,9 @@ class NameDispatchExtensionManager(DispatchExtensionManager):
invoke_kwds=invoke_kwds,
propagate_map_exceptions=propagate_map_exceptions,
)
+
+ def _init_plugins(self, extensions):
+ super(NameDispatchExtensionManager, self)._init_plugins(extensions)
self.by_name = dict((e.name, e) for e in self.extensions)
def map(self, names, func, *args, **kwds):
diff --git a/stevedore/driver.py b/stevedore/driver.py
index ea2e122..56e408d 100644
--- a/stevedore/driver.py
+++ b/stevedore/driver.py
@@ -30,16 +30,48 @@ class DriverManager(NamedExtensionManager):
invoke_args=invoke_args,
invoke_kwds=invoke_kwds,
)
+
+ @classmethod
+ def make_test_instance(cls, extension, namespace='TESTING',
+ propagate_map_exceptions=False):
+ """Construct a test DriverManager
+
+ Test instances are passed a list of extensions to work from rather
+ than loading them from entry points. DriverManager test instances use
+ the name argument to determine which of the available_extensions should
+ be used as the driver.
+
+ :param extension: Pre-configured Extension instances
+ :type extension: list of :class:`~stevedore.extension.Extension`
+ :param namespace: The namespace for the manager; used only for
+ identification since the extensions are passed in.
+ :type namespace: str
+ :param propagate_map_exceptions: Boolean controlling whether exceptions
+ are propagated up through the map call or whether they are logged
+ and then ignored
+ :type propagate_map_exceptions: bool
+ :return: The manager instance, initialized for testing
+
+ """
+
+ o = super(DriverManager, cls).make_test_instance(
+ [extension], namespace=namespace,
+ propagate_map_exceptions=propagate_map_exceptions)
+ return o
+
+ def _init_plugins(self, extensions, propagate_map_exceptions=False):
+ super(DriverManager, self)._init_plugins(extensions)
+
if not self.extensions:
+ name = self._names[0]
raise RuntimeError('No %r driver found, looking for %r' %
- (namespace, name))
+ (self.namespace, name))
if len(self.extensions) > 1:
+ discovered_drivers = ','.join(e.entry_point_target
+ for e in self.extensions)
+
raise RuntimeError('Multiple %r drivers found: %s' %
- (namespace,
- ','.join('%s:%s' % (e.entry_point.module_name,
- e.entry_point.attrs[0])
- for e in self.extensions))
- )
+ (self.namespace, discovered_drivers))
def __call__(self, func, *args, **kwds):
"""Invokes func() for the single loaded extension.
diff --git a/stevedore/enabled.py b/stevedore/enabled.py
index 5cb0033..b6260b1 100644
--- a/stevedore/enabled.py
+++ b/stevedore/enabled.py
@@ -31,7 +31,7 @@ class EnabledExtensionManager(ExtensionManager):
:param propagate_map_exceptions: Boolean controlling whether exceptions
are propagated up through the map call or whether they are logged and
then ignored
- :type invoke_on_load: bool
+ :type propagate_map_exceptions: bool
"""
diff --git a/stevedore/extension.py b/stevedore/extension.py
index 1ac87e8..2e29a95 100644
--- a/stevedore/extension.py
+++ b/stevedore/extension.py
@@ -34,6 +34,16 @@ class Extension(object):
self.plugin = plugin
self.obj = obj
+ @property
+ def entry_point_target(self):
+ """The module and attribute referenced by this extension's entry_point.
+
+ :return: A string representation of the target of the entry point in
+ 'dotted.module:object' format.
+ """
+ return '%s:%s' % (self.entry_point.module_name,
+ self.entry_point.attrs[0])
+
class ExtensionManager(object):
"""Base class for all of the other managers.
@@ -54,7 +64,7 @@ class ExtensionManager(object):
:param propagate_map_exceptions: Boolean controlling whether exceptions
are propagated up through the map call or whether they are logged and
then ignored
- :type invoke_on_load: bool
+ :type propagate_map_exceptions: bool
"""
@@ -63,11 +73,46 @@ class ExtensionManager(object):
invoke_args=(),
invoke_kwds={},
propagate_map_exceptions=False):
+ self._init_attributes(
+ namespace, propagate_map_exceptions=propagate_map_exceptions)
+ extensions = self._load_plugins(invoke_on_load,
+ invoke_args,
+ invoke_kwds)
+ self._init_plugins(extensions)
+
+ @classmethod
+ def make_test_instance(cls, extensions, namespace='TESTING',
+ propagate_map_exceptions=False):
+ """Construct a test ExtensionManager
+
+ Test instances are passed a list of extensions to work from rather
+ than loading them from entry points.
+
+ :param extensions: Pre-configured Extension instances to use
+ :type extensions: list of :class:`~stevedore.extension.Extension`
+ :param namespace: The namespace for the manager; used only for
+ identification since the extensions are passed in.
+ :type namespace: str
+ :param propagate_map_exceptions: When calling map, controls whether
+ exceptions are propagated up through the map call or whether they
+ are logged and then ignored
+ :type propagate_map_exceptions: bool
+ :return: The manager instance, initialized for testing
+
+ """
+
+ o = cls.__new__(cls)
+ o._init_attributes(namespace,
+ propagate_map_exceptions=propagate_map_exceptions)
+ o._init_plugins(extensions)
+ return o
+
+ def _init_attributes(self, namespace, propagate_map_exceptions=False):
self.namespace = namespace
self.propagate_map_exceptions = propagate_map_exceptions
- self.extensions = self._load_plugins(invoke_on_load,
- invoke_args,
- invoke_kwds)
+
+ def _init_plugins(self, extensions):
+ self.extensions = extensions
self._extensions_by_name = None
ENTRY_POINT_CACHE = {}
diff --git a/stevedore/hook.py b/stevedore/hook.py
index c1cec7f..b7cdef8 100644
--- a/stevedore/hook.py
+++ b/stevedore/hook.py
@@ -23,7 +23,6 @@ class HookManager(NamedExtensionManager):
def __init__(self, namespace, name,
invoke_on_load=False, invoke_args=(), invoke_kwds={}):
- self._name = name
super(HookManager, self).__init__(
namespace,
[name],
@@ -32,6 +31,13 @@ class HookManager(NamedExtensionManager):
invoke_kwds=invoke_kwds,
)
+ def _init_attributes(self, namespace, names, name_order=False,
+ propagate_map_exceptions=False):
+ super(HookManager, self)._init_attributes(
+ namespace, names,
+ propagate_map_exceptions=propagate_map_exceptions)
+ self._name = names[0]
+
def __getitem__(self, name):
"""Return the named extensions.
diff --git a/stevedore/named.py b/stevedore/named.py
index e4cf857..b4f620c 100644
--- a/stevedore/named.py
+++ b/stevedore/named.py
@@ -28,24 +28,62 @@ class NamedExtensionManager(ExtensionManager):
:param propagate_map_exceptions: Boolean controlling whether exceptions
are propagated up through the map call or whether they are logged and
then ignored
- :type invoke_on_load: bool
+ :type propagate_map_exceptions: bool
+
"""
def __init__(self, namespace, names,
invoke_on_load=False, invoke_args=(), invoke_kwds={},
name_order=False, propagate_map_exceptions=False):
+ self._init_attributes(
+ namespace, names, name_order=name_order,
+ propagate_map_exceptions=propagate_map_exceptions)
+ extensions = self._load_plugins(invoke_on_load,
+ invoke_args,
+ invoke_kwds)
+ self._init_plugins(extensions)
+
+ @classmethod
+ def make_test_instance(cls, extensions, namespace='TESTING',
+ propagate_map_exceptions=False):
+ """Construct a test NamedExtensionManager
+
+ Test instances are passed a list of extensions to use rather than
+ loading them from entry points.
+
+ :param extensions: Pre-configured Extension instances
+ :type extensions: list of :class:`~stevedore.extension.Extension`
+ :param namespace: The namespace for the manager; used only for
+ identification since the extensions are passed in.
+ :type namespace: str
+ :param propagate_map_exceptions: Boolean controlling whether exceptions
+ are propagated up through the map call or whether they are logged
+ and then ignored
+ :type propagate_map_exceptions: bool
+ :return: The manager instance, initialized for testing
+
+ """
+
+ o = cls.__new__(cls)
+ names = [e.name for e in extensions]
+ o._init_attributes(namespace, names,
+ propagate_map_exceptions=propagate_map_exceptions)
+ o._init_plugins(extensions)
+ return o
+
+ def _init_attributes(self, namespace, names, name_order=False,
+ propagate_map_exceptions=False):
+ super(NamedExtensionManager, self)._init_attributes(
+ namespace, propagate_map_exceptions=propagate_map_exceptions)
+
self._names = names
- super(NamedExtensionManager, self).__init__(
- namespace,
- invoke_on_load=invoke_on_load,
- invoke_args=invoke_args,
- invoke_kwds=invoke_kwds,
- propagate_map_exceptions=propagate_map_exceptions,
- )
+ self._name_order = name_order
+
+ def _init_plugins(self, extensions):
+ super(NamedExtensionManager, self)._init_plugins(extensions)
- if name_order:
- ext_map = dict((x.name, x) for x in self.extensions)
- self.extensions = [ext_map[n] for n in names]
+ if self._name_order:
+ self.extensions = [self[n] for n in self._names]
def _load_one_plugin(self, ep, invoke_on_load, invoke_args, invoke_kwds):
# Check the name before going any further to prevent
diff --git a/stevedore/tests/test_test_manager.py b/stevedore/tests/test_test_manager.py
new file mode 100644
index 0000000..aa995b2
--- /dev/null
+++ b/stevedore/tests/test_test_manager.py
@@ -0,0 +1,278 @@
+from mock import Mock, sentinel
+from nose.tools import raises
+from stevedore import (ExtensionManager, NamedExtensionManager, HookManager,
+ DriverManager, EnabledExtensionManager)
+from stevedore.dispatch import (DispatchExtensionManager,
+ NameDispatchExtensionManager)
+from stevedore.extension import Extension
+
+
+test_extension = Extension('test_extension', None, None, None)
+test_extension2 = Extension('another_one', None, None, None)
+
+mock_entry_point = Mock(module_name='test.extension', attrs=['obj'])
+a_driver = Extension('test_driver', mock_entry_point, sentinel.driver_plugin,
+ sentinel.driver_obj)
+
+
+# base ExtensionManager
+
+def test_instance_should_use_supplied_extensions():
+ extensions = [test_extension, test_extension2]
+ em = ExtensionManager.make_test_instance(extensions)
+
+ assert extensions == em.extensions
+
+
+def test_instance_should_have_default_namespace():
+ em = ExtensionManager.make_test_instance([])
+
+ assert em.namespace
+
+
+def test_instance_should_use_supplied_namespace():
+ namespace = 'testing.1.2.3'
+ em = ExtensionManager.make_test_instance([], namespace=namespace)
+
+ assert namespace == em.namespace
+
+
+def test_extension_name_should_be_listed():
+ em = ExtensionManager.make_test_instance([test_extension])
+
+ assert test_extension.name in em.names()
+
+
+def test_iterator_should_yield_extension():
+ em = ExtensionManager.make_test_instance([test_extension])
+
+ assert test_extension == next(iter(em))
+
+
+def test_manager_should_allow_name_access():
+ em = ExtensionManager.make_test_instance([test_extension])
+
+ assert test_extension == em[test_extension.name]
+
+
+def test_manager_should_call():
+ em = ExtensionManager.make_test_instance([test_extension])
+ func = Mock()
+
+ em.map(func)
+
+ func.assert_called_once_with(test_extension)
+
+
+def test_manager_should_call_all():
+ em = ExtensionManager.make_test_instance([test_extension2,
+ test_extension])
+ func = Mock()
+
+ em.map(func)
+
+ func.assert_any_call(test_extension2)
+ func.assert_any_call(test_extension)
+
+
+def test_manager_return_values():
+ def mapped(ext, *args, **kwds):
+ return ext.name
+
+ em = ExtensionManager.make_test_instance([test_extension2,
+ test_extension])
+ results = em.map(mapped)
+ assert sorted(results) == ['another_one', 'test_extension']
+
+
+def test_manager_should_eat_exceptions():
+ em = ExtensionManager.make_test_instance([test_extension])
+
+ func = Mock(side_effect=RuntimeError('hard coded error'))
+
+ results = em.map(func, 1, 2, a='A', b='B')
+ assert results == []
+
+
+@raises(RuntimeError)
+def test_manager_should_propagate_exceptions():
+ em = ExtensionManager.make_test_instance([test_extension],
+ propagate_map_exceptions=True)
+ func = Mock(side_effect=RuntimeError('hard coded error'))
+
+ em.map(func, 1, 2, a='A', b='B')
+
+
+# NamedExtensionManager
+
+def test_named_manager_should_use_supplied_extensions():
+ extensions = [test_extension, test_extension2]
+ em = NamedExtensionManager.make_test_instance(extensions)
+
+ assert extensions == em.extensions
+
+
+def test_named_manager_should_have_default_namespace():
+ em = NamedExtensionManager.make_test_instance([])
+
+ assert em.namespace
+
+
+def test_named_manager_should_use_supplied_namespace():
+ namespace = 'testing.1.2.3'
+ em = NamedExtensionManager.make_test_instance([], namespace=namespace)
+
+ assert namespace == em.namespace
+
+
+def test_named_manager_should_populate_names():
+ extensions = [test_extension, test_extension2]
+ em = NamedExtensionManager.make_test_instance(extensions)
+
+ assert ['test_extension', 'another_one'] == em.names()
+
+
+# HookManager
+
+def test_hook_manager_should_use_supplied_extensions():
+ extensions = [test_extension, test_extension2]
+ em = HookManager.make_test_instance(extensions)
+
+ assert extensions == em.extensions
+
+
+def test_hook_manager_should_be_first_extension_name():
+ extensions = [test_extension, test_extension2]
+ em = HookManager.make_test_instance(extensions)
+
+ # This will raise KeyError if the names don't match
+ assert em[test_extension.name]
+
+
+def test_hook_manager_should_have_default_namespace():
+ em = HookManager.make_test_instance([test_extension])
+
+ assert em.namespace
+
+
+def test_hook_manager_should_use_supplied_namespace():
+ namespace = 'testing.1.2.3'
+ em = HookManager.make_test_instance([test_extension], namespace=namespace)
+
+ assert namespace == em.namespace
+
+
+def test_hook_manager_should_return_named_extensions():
+ hook1 = Extension('captain', None, None, None)
+ hook2 = Extension('captain', None, None, None)
+
+ em = HookManager.make_test_instance([hook1, hook2])
+
+ assert [hook1, hook2] == em['captain']
+
+
+# DriverManager
+
+def test_driver_manager_should_use_supplied_extension():
+ em = DriverManager.make_test_instance(a_driver)
+
+ assert [a_driver] == em.extensions
+
+
+def test_driver_manager_should_have_default_namespace():
+ em = DriverManager.make_test_instance(a_driver)
+
+ assert em.namespace
+
+
+def test_driver_manager_should_use_supplied_namespace():
+ namespace = 'testing.1.2.3'
+ em = DriverManager.make_test_instance(a_driver, namespace=namespace)
+
+ assert namespace == em.namespace
+
+
+def test_instance_should_use_driver_name():
+ em = DriverManager.make_test_instance(a_driver)
+
+ assert ['test_driver'] == em.names()
+
+
+def test_instance_call():
+ def invoke(ext, *args, **kwds):
+ return ext.name, args, kwds
+
+ em = DriverManager.make_test_instance(a_driver)
+ result = em(invoke, 'a', b='C')
+
+ assert result == ('test_driver', ('a',), {'b': 'C'})
+
+
+def test_instance_driver_property():
+ em = DriverManager.make_test_instance(a_driver)
+
+ assert sentinel.driver_obj == em.driver
+
+
+# EnabledExtensionManager
+
+def test_enabled_instance_should_use_supplied_extensions():
+ extensions = [test_extension, test_extension2]
+ em = EnabledExtensionManager.make_test_instance(extensions)
+
+ assert extensions == em.extensions
+
+
+# DispatchExtensionManager
+
+def test_dispatch_instance_should_use_supplied_extensions():
+ extensions = [test_extension, test_extension2]
+ em = DispatchExtensionManager.make_test_instance(extensions)
+
+ assert extensions == em.extensions
+
+
+def test_dispatch_map_should_invoke_filter_for_extensions():
+ em = DispatchExtensionManager.make_test_instance([test_extension,
+ test_extension2])
+
+ filter_func = Mock(return_value=False)
+
+ args = ('A',)
+ kw = {'big': 'Cheese'}
+
+ em.map(filter_func, None, *args, **kw)
+
+ filter_func.assert_any_call(test_extension, *args, **kw)
+ filter_func.assert_any_call(test_extension2, *args, **kw)
+
+
+# NameDispatchExtensionManager
+
+def test_name_dispatch_instance_should_use_supplied_extensions():
+ extensions = [test_extension, test_extension2]
+ em = NameDispatchExtensionManager.make_test_instance(extensions)
+
+ assert extensions == em.extensions
+
+
+def test_name_dispatch_instance_should_build_extension_name_map():
+ extensions = [test_extension, test_extension2]
+ em = NameDispatchExtensionManager.make_test_instance(extensions)
+
+ assert test_extension == em.by_name[test_extension.name]
+ assert test_extension2 == em.by_name[test_extension2.name]
+
+
+def test_named_dispatch_map_should_invoke_filter_for_extensions():
+ em = NameDispatchExtensionManager.make_test_instance([test_extension,
+ test_extension2])
+
+ func = Mock()
+
+ args = ('A',)
+ kw = {'BIGGER': 'Cheese'}
+
+ em.map(['test_extension'], func, *args, **kw)
+
+ func.assert_called_once_with(test_extension, *args, **kw)