diff options
author | Daniel Rocco <drocco@thebrightlink.com> | 2013-06-30 08:52:14 -0400 |
---|---|---|
committer | Daniel Rocco <drocco@thebrightlink.com> | 2013-11-23 23:12:46 -0500 |
commit | acaf47782e25b1a4eb6056e72034335d03e00e3b (patch) | |
tree | 9953b7d95fe6302ff502ee6294d94e3ee2792b71 | |
parent | fdde242a8ebf655e3bc81d753dc9d8b9869ed29e (diff) | |
download | stevedore-acaf47782e25b1a4eb6056e72034335d03e00e3b.tar.gz |
add test instance factory for the base, named, and hook managers
The class method make_test_instance constructs manager instances using a
predefined list of extensions, rather than loading from entry points.
-rw-r--r-- | stevedore/extension.py | 35 | ||||
-rw-r--r-- | stevedore/hook.py | 24 | ||||
-rw-r--r-- | stevedore/named.py | 49 | ||||
-rw-r--r-- | stevedore/tests/test_test_manager.py | 117 |
4 files changed, 219 insertions, 6 deletions
diff --git a/stevedore/extension.py b/stevedore/extension.py index 1ac87e8..a29af8e 100644 --- a/stevedore/extension.py +++ b/stevedore/extension.py @@ -54,7 +54,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 """ @@ -64,10 +64,37 @@ class ExtensionManager(object): invoke_kwds={}, propagate_map_exceptions=False): self.namespace = namespace + extensions = self._load_plugins(invoke_on_load, + invoke_args, + invoke_kwds) + self._init_plugins(extensions, + propagate_map_exceptions=propagate_map_exceptions) + + @classmethod + def make_test_instance(cls, extensions, 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 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_plugins(extensions, + propagate_map_exceptions=propagate_map_exceptions) + return o + + def _init_plugins(self, extensions, propagate_map_exceptions=False): + self.extensions = extensions self.propagate_map_exceptions = propagate_map_exceptions - self.extensions = self._load_plugins(invoke_on_load, - invoke_args, - invoke_kwds) self._extensions_by_name = None ENTRY_POINT_CACHE = {} diff --git a/stevedore/hook.py b/stevedore/hook.py index c1cec7f..29f845a 100644 --- a/stevedore/hook.py +++ b/stevedore/hook.py @@ -32,6 +32,30 @@ class HookManager(NamedExtensionManager): invoke_kwds=invoke_kwds, ) + @classmethod + def make_test_instance(cls, available_extensions, name): + """Construct a test HookManager + + Test instances are passed a list of extensions to work from rather + than loading them from entry points, filtering the available + extensions to only those extensions whose name matches the name + argument. + + :param available_extensions: Pre-configured Extension instances + available for use + :type available_extensions: list of + :class:`~stevedore.extension.Extension` + :param name: The name of the hooks to use. + :type name: str + :return: The manager instance, initialized for testing + + """ + + o = super(HookManager, cls).make_test_instance(available_extensions, + [name]) + o._name = name + return o + def __getitem__(self, name): """Return the named extensions. diff --git a/stevedore/named.py b/stevedore/named.py index e26667f..9708f99 100644 --- a/stevedore/named.py +++ b/stevedore/named.py @@ -28,13 +28,15 @@ 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._names = names + self._name_order = name_order super(NamedExtensionManager, self).__init__( namespace, invoke_on_load=invoke_on_load, @@ -43,7 +45,50 @@ class NamedExtensionManager(ExtensionManager): propagate_map_exceptions=propagate_map_exceptions, ) - if name_order: + @classmethod + def make_test_instance(cls, available_extensions, names, + name_order=False, propagate_map_exceptions=False): + """Construct a test NamedExtensionManager + + Test instances are passed a list of extensions to work from rather + than loading them from entry points, filtering the available + extensions to only those extensions whose names are listed in the + names argument. + + :param available_extensions: Pre-configured Extension instances + available for use + :type available_extensions: list of + :class:`~stevedore.extension.Extension` + :param names: The names of the extensions to use. + :type names: list(str) + :param name_order: If true, sort the extensions to match the order + used in ``names``. + :type name_order: bool + :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) + o._names = names + o._name_order = name_order + + # simulate excluding plugins not listed in names, which normally + # happens in _load_one_plugin + extensions = [extension for extension in available_extensions + if extension.name in names] + o._init_plugins(extensions, + propagate_map_exceptions=propagate_map_exceptions) + return o + + def _init_plugins(self, extensions, propagate_map_exceptions=False): + super(NamedExtensionManager, self)._init_plugins( + extensions, propagate_map_exceptions=propagate_map_exceptions) + + 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): diff --git a/stevedore/tests/test_test_manager.py b/stevedore/tests/test_test_manager.py new file mode 100644 index 0000000..9b7830b --- /dev/null +++ b/stevedore/tests/test_test_manager.py @@ -0,0 +1,117 @@ +from mock import Mock +from stevedore import NamedExtensionManager, HookManager +from stevedore.extension import Extension, ExtensionManager + + +test_extension = Extension('test_extension', None, None, None) +test_extension2 = Extension('another_one', None, None, None) +excluded = Extension('excluded', None, None, None) +unwanted = Extension('unwanted', None, None, None) + + +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_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 == [] + + +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')) + + try: + em.map(func, 1, 2, a='A', b='B') + assert False, 'DID NOT RAISE!' + except RuntimeError: + pass + + +def test_named_manager_should_include_named_extensions(): + em = NamedExtensionManager.make_test_instance([excluded, + test_extension, + unwanted, + test_extension2], + ['test_extension', + 'another_one']) + + extensions = list(em) + + assert test_extension in extensions + assert test_extension2 in extensions + + +def test_named_manager_should_not_include_unnamed_extensions(): + em = NamedExtensionManager.make_test_instance([excluded, + test_extension, + unwanted, + test_extension2], + ['test_extension', + 'another_one']) + + extensions = list(em) + + assert excluded not in extensions + assert unwanted not in extensions + + +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], 'captain') + + assert [hook1, hook2] == em['captain'] + + +def test_hook_manager_should_not_include_unnamed_extensions(): + em = HookManager.make_test_instance([excluded, test_extension, + unwanted, test_extension2], + 'test_extension') + + extensions = em['test_extension'] + + assert test_extension2 not in extensions + assert excluded not in extensions + assert unwanted not in extensions |