1823b8294SYaroslav Brustinov"""
2823b8294SYaroslav BrustinovPlugin Manager
3823b8294SYaroslav Brustinov--------------
4823b8294SYaroslav Brustinov
5823b8294SYaroslav BrustinovA plugin manager class is used to load plugins, manage the list of
6823b8294SYaroslav Brustinovloaded plugins, and proxy calls to those plugins.
7823b8294SYaroslav Brustinov
8823b8294SYaroslav BrustinovThe plugin managers provided with nose are:
9823b8294SYaroslav Brustinov
10823b8294SYaroslav Brustinov:class:`PluginManager`
11823b8294SYaroslav Brustinov    This manager doesn't implement loadPlugins, so it can only work
12823b8294SYaroslav Brustinov    with a static list of plugins.
13823b8294SYaroslav Brustinov
14823b8294SYaroslav Brustinov:class:`BuiltinPluginManager`
15823b8294SYaroslav Brustinov    This manager loads plugins referenced in ``nose.plugins.builtin``.
16823b8294SYaroslav Brustinov
17823b8294SYaroslav Brustinov:class:`EntryPointPluginManager`
18823b8294SYaroslav Brustinov    This manager uses setuptools entrypoints to load plugins.
19823b8294SYaroslav Brustinov
20823b8294SYaroslav Brustinov:class:`ExtraPluginsPluginManager`
21823b8294SYaroslav Brustinov    This manager loads extra plugins specified with the keyword
22823b8294SYaroslav Brustinov    `addplugins`.
23823b8294SYaroslav Brustinov
24823b8294SYaroslav Brustinov:class:`DefaultPluginMananger`
25823b8294SYaroslav Brustinov    This is the manager class that will be used by default. If
26823b8294SYaroslav Brustinov    setuptools is installed, it is a subclass of
27823b8294SYaroslav Brustinov    :class:`EntryPointPluginManager` and :class:`BuiltinPluginManager`;
28823b8294SYaroslav Brustinov    otherwise, an alias to :class:`BuiltinPluginManager`.
29823b8294SYaroslav Brustinov
30823b8294SYaroslav Brustinov:class:`RestrictedPluginManager`
31823b8294SYaroslav Brustinov    This manager is for use in test runs where some plugin calls are
32823b8294SYaroslav Brustinov    not available, such as runs started with ``python setup.py test``,
33823b8294SYaroslav Brustinov    where the test runner is the default unittest :class:`TextTestRunner`. It
34823b8294SYaroslav Brustinov    is a subclass of :class:`DefaultPluginManager`.
35823b8294SYaroslav Brustinov
36823b8294SYaroslav BrustinovWriting a plugin manager
37823b8294SYaroslav Brustinov========================
38823b8294SYaroslav Brustinov
39823b8294SYaroslav BrustinovIf you want to load plugins via some other means, you can write a
40823b8294SYaroslav Brustinovplugin manager and pass an instance of your plugin manager class when
41823b8294SYaroslav Brustinovinstantiating the :class:`nose.config.Config` instance that you pass to
42823b8294SYaroslav Brustinov:class:`TestProgram` (or :func:`main` or :func:`run`).
43823b8294SYaroslav Brustinov
44823b8294SYaroslav BrustinovTo implement your plugin loading scheme, implement ``loadPlugins()``,
45823b8294SYaroslav Brustinovand in that method, call ``addPlugin()`` with an instance of each plugin
46823b8294SYaroslav Brustinovyou wish to make available. Make sure to call
47823b8294SYaroslav Brustinov``super(self).loadPlugins()`` as well if have subclassed a manager
48823b8294SYaroslav Brustinovother than ``PluginManager``.
49823b8294SYaroslav Brustinov
50823b8294SYaroslav Brustinov"""
51823b8294SYaroslav Brustinovimport inspect
52823b8294SYaroslav Brustinovimport logging
53823b8294SYaroslav Brustinovimport os
54823b8294SYaroslav Brustinovimport sys
55823b8294SYaroslav Brustinovfrom itertools import chain as iterchain
56823b8294SYaroslav Brustinovfrom warnings import warn
57823b8294SYaroslav Brustinovimport nose.config
58823b8294SYaroslav Brustinovfrom nose.failure import Failure
59823b8294SYaroslav Brustinovfrom nose.plugins.base import IPluginInterface
60823b8294SYaroslav Brustinovfrom nose.pyversion import sort_list
61823b8294SYaroslav Brustinov
62823b8294SYaroslav Brustinovtry:
63823b8294SYaroslav Brustinov    import cPickle as pickle
64823b8294SYaroslav Brustinovexcept:
65823b8294SYaroslav Brustinov    import pickle
66823b8294SYaroslav Brustinovtry:
67823b8294SYaroslav Brustinov    from cStringIO import StringIO
68823b8294SYaroslav Brustinovexcept:
69823b8294SYaroslav Brustinov    from StringIO import StringIO
70823b8294SYaroslav Brustinov
71823b8294SYaroslav Brustinov
72823b8294SYaroslav Brustinov__all__ = ['DefaultPluginManager', 'PluginManager', 'EntryPointPluginManager',
73823b8294SYaroslav Brustinov           'BuiltinPluginManager', 'RestrictedPluginManager']
74823b8294SYaroslav Brustinov
75823b8294SYaroslav Brustinovlog = logging.getLogger(__name__)
76823b8294SYaroslav Brustinov
77823b8294SYaroslav Brustinov
78823b8294SYaroslav Brustinovclass PluginProxy(object):
79823b8294SYaroslav Brustinov    """Proxy for plugin calls. Essentially a closure bound to the
80823b8294SYaroslav Brustinov    given call and plugin list.
81823b8294SYaroslav Brustinov
82823b8294SYaroslav Brustinov    The plugin proxy also must be bound to a particular plugin
83823b8294SYaroslav Brustinov    interface specification, so that it knows what calls are available
84823b8294SYaroslav Brustinov    and any special handling that is required for each call.
85823b8294SYaroslav Brustinov    """
86823b8294SYaroslav Brustinov    interface = IPluginInterface
87823b8294SYaroslav Brustinov    def __init__(self, call, plugins):
88823b8294SYaroslav Brustinov        try:
89823b8294SYaroslav Brustinov            self.method = getattr(self.interface, call)
90823b8294SYaroslav Brustinov        except AttributeError:
91823b8294SYaroslav Brustinov            raise AttributeError("%s is not a valid %s method"
92823b8294SYaroslav Brustinov                                 % (call, self.interface.__name__))
93823b8294SYaroslav Brustinov        self.call = self.makeCall(call)
94823b8294SYaroslav Brustinov        self.plugins = []
95823b8294SYaroslav Brustinov        for p in plugins:
96823b8294SYaroslav Brustinov            self.addPlugin(p, call)
97823b8294SYaroslav Brustinov
98823b8294SYaroslav Brustinov    def __call__(self, *arg, **kw):
99823b8294SYaroslav Brustinov        return self.call(*arg, **kw)
100823b8294SYaroslav Brustinov
101823b8294SYaroslav Brustinov    def addPlugin(self, plugin, call):
102823b8294SYaroslav Brustinov        """Add plugin to my list of plugins to call, if it has the attribute
103823b8294SYaroslav Brustinov        I'm bound to.
104823b8294SYaroslav Brustinov        """
105823b8294SYaroslav Brustinov        meth = getattr(plugin, call, None)
106823b8294SYaroslav Brustinov        if meth is not None:
107823b8294SYaroslav Brustinov            if call == 'loadTestsFromModule' and \
108823b8294SYaroslav Brustinov                    len(inspect.getargspec(meth)[0]) == 2:
109823b8294SYaroslav Brustinov                orig_meth = meth
110823b8294SYaroslav Brustinov                meth = lambda module, path, **kwargs: orig_meth(module)
111823b8294SYaroslav Brustinov            self.plugins.append((plugin, meth))
112823b8294SYaroslav Brustinov
113823b8294SYaroslav Brustinov    def makeCall(self, call):
114823b8294SYaroslav Brustinov        if call == 'loadTestsFromNames':
115823b8294SYaroslav Brustinov            # special case -- load tests from names behaves somewhat differently
116823b8294SYaroslav Brustinov            # from other chainable calls, because plugins return a tuple, only
117823b8294SYaroslav Brustinov            # part of which can be chained to the next plugin.
118823b8294SYaroslav Brustinov            return self._loadTestsFromNames
119823b8294SYaroslav Brustinov
120823b8294SYaroslav Brustinov        meth = self.method
121823b8294SYaroslav Brustinov        if getattr(meth, 'generative', False):
122823b8294SYaroslav Brustinov            # call all plugins and yield a flattened iterator of their results
123823b8294SYaroslav Brustinov            return lambda *arg, **kw: list(self.generate(*arg, **kw))
124823b8294SYaroslav Brustinov        elif getattr(meth, 'chainable', False):
125823b8294SYaroslav Brustinov            return self.chain
126823b8294SYaroslav Brustinov        else:
127823b8294SYaroslav Brustinov            # return a value from the first plugin that returns non-None
128823b8294SYaroslav Brustinov            return self.simple
129823b8294SYaroslav Brustinov
130823b8294SYaroslav Brustinov    def chain(self, *arg, **kw):
131823b8294SYaroslav Brustinov        """Call plugins in a chain, where the result of each plugin call is
132823b8294SYaroslav Brustinov        sent to the next plugin as input. The final output result is returned.
133823b8294SYaroslav Brustinov        """
134823b8294SYaroslav Brustinov        result = None
135823b8294SYaroslav Brustinov        # extract the static arguments (if any) from arg so they can
136823b8294SYaroslav Brustinov        # be passed to each plugin call in the chain
137823b8294SYaroslav Brustinov        static = [a for (static, a)
138823b8294SYaroslav Brustinov                  in zip(getattr(self.method, 'static_args', []), arg)
139823b8294SYaroslav Brustinov                  if static]
140823b8294SYaroslav Brustinov        for p, meth in self.plugins:
141823b8294SYaroslav Brustinov            result = meth(*arg, **kw)
142823b8294SYaroslav Brustinov            arg = static[:]
143823b8294SYaroslav Brustinov            arg.append(result)
144823b8294SYaroslav Brustinov        return result
145823b8294SYaroslav Brustinov
146823b8294SYaroslav Brustinov    def generate(self, *arg, **kw):
147823b8294SYaroslav Brustinov        """Call all plugins, yielding each item in each non-None result.
148823b8294SYaroslav Brustinov        """
149823b8294SYaroslav Brustinov        for p, meth in self.plugins:
150823b8294SYaroslav Brustinov            result = None
151823b8294SYaroslav Brustinov            try:
152823b8294SYaroslav Brustinov                result = meth(*arg, **kw)
153823b8294SYaroslav Brustinov                if result is not None:
154823b8294SYaroslav Brustinov                    for r in result:
155823b8294SYaroslav Brustinov                        yield r
156823b8294SYaroslav Brustinov            except (KeyboardInterrupt, SystemExit):
157823b8294SYaroslav Brustinov                raise
158823b8294SYaroslav Brustinov            except:
159823b8294SYaroslav Brustinov                exc = sys.exc_info()
160823b8294SYaroslav Brustinov                yield Failure(*exc)
161823b8294SYaroslav Brustinov                continue
162823b8294SYaroslav Brustinov
163823b8294SYaroslav Brustinov    def simple(self, *arg, **kw):
164823b8294SYaroslav Brustinov        """Call all plugins, returning the first non-None result.
165823b8294SYaroslav Brustinov        """
166823b8294SYaroslav Brustinov        for p, meth in self.plugins:
167823b8294SYaroslav Brustinov            result = meth(*arg, **kw)
168823b8294SYaroslav Brustinov            if result is not None:
169823b8294SYaroslav Brustinov                return result
170823b8294SYaroslav Brustinov
171823b8294SYaroslav Brustinov    def _loadTestsFromNames(self, names, module=None):
172823b8294SYaroslav Brustinov        """Chainable but not quite normal. Plugins return a tuple of
173823b8294SYaroslav Brustinov        (tests, names) after processing the names. The tests are added
174823b8294SYaroslav Brustinov        to a suite that is accumulated throughout the full call, while
175823b8294SYaroslav Brustinov        names are input for the next plugin in the chain.
176823b8294SYaroslav Brustinov        """
177823b8294SYaroslav Brustinov        suite = []
178823b8294SYaroslav Brustinov        for p, meth in self.plugins:
179823b8294SYaroslav Brustinov            result = meth(names, module=module)
180823b8294SYaroslav Brustinov            if result is not None:
181823b8294SYaroslav Brustinov                suite_part, names = result
182823b8294SYaroslav Brustinov                if suite_part:
183823b8294SYaroslav Brustinov                    suite.extend(suite_part)
184823b8294SYaroslav Brustinov        return suite, names
185823b8294SYaroslav Brustinov
186823b8294SYaroslav Brustinov
187823b8294SYaroslav Brustinovclass NoPlugins(object):
188823b8294SYaroslav Brustinov    """Null Plugin manager that has no plugins."""
189823b8294SYaroslav Brustinov    interface = IPluginInterface
190823b8294SYaroslav Brustinov    def __init__(self):
191823b8294SYaroslav Brustinov        self._plugins = self.plugins = ()
192823b8294SYaroslav Brustinov
193823b8294SYaroslav Brustinov    def __iter__(self):
194823b8294SYaroslav Brustinov        return ()
195823b8294SYaroslav Brustinov
196823b8294SYaroslav Brustinov    def _doNothing(self, *args, **kwds):
197823b8294SYaroslav Brustinov        pass
198823b8294SYaroslav Brustinov
199823b8294SYaroslav Brustinov    def _emptyIterator(self, *args, **kwds):
200823b8294SYaroslav Brustinov        return ()
201823b8294SYaroslav Brustinov
202823b8294SYaroslav Brustinov    def __getattr__(self, call):
203823b8294SYaroslav Brustinov        method = getattr(self.interface, call)
204823b8294SYaroslav Brustinov        if getattr(method, "generative", False):
205823b8294SYaroslav Brustinov            return self._emptyIterator
206823b8294SYaroslav Brustinov        else:
207823b8294SYaroslav Brustinov            return self._doNothing
208823b8294SYaroslav Brustinov
209823b8294SYaroslav Brustinov    def addPlugin(self, plug):
210823b8294SYaroslav Brustinov        raise NotImplementedError()
211823b8294SYaroslav Brustinov
212823b8294SYaroslav Brustinov    def addPlugins(self, plugins):
213823b8294SYaroslav Brustinov        raise NotImplementedError()
214823b8294SYaroslav Brustinov
215823b8294SYaroslav Brustinov    def configure(self, options, config):
216823b8294SYaroslav Brustinov        pass
217823b8294SYaroslav Brustinov
218823b8294SYaroslav Brustinov    def loadPlugins(self):
219823b8294SYaroslav Brustinov        pass
220823b8294SYaroslav Brustinov
221823b8294SYaroslav Brustinov    def sort(self):
222823b8294SYaroslav Brustinov        pass
223823b8294SYaroslav Brustinov
224823b8294SYaroslav Brustinov
225823b8294SYaroslav Brustinovclass PluginManager(object):
226823b8294SYaroslav Brustinov    """Base class for plugin managers. PluginManager is intended to be
227823b8294SYaroslav Brustinov    used only with a static list of plugins. The loadPlugins() implementation
228823b8294SYaroslav Brustinov    only reloads plugins from _extraplugins to prevent those from being
229823b8294SYaroslav Brustinov    overridden by a subclass.
230823b8294SYaroslav Brustinov
231823b8294SYaroslav Brustinov    The basic functionality of a plugin manager is to proxy all unknown
232823b8294SYaroslav Brustinov    attributes through a ``PluginProxy`` to a list of plugins.
233823b8294SYaroslav Brustinov
234823b8294SYaroslav Brustinov    Note that the list of plugins *may not* be changed after the first plugin
235823b8294SYaroslav Brustinov    call.
236823b8294SYaroslav Brustinov    """
237823b8294SYaroslav Brustinov    proxyClass = PluginProxy
238823b8294SYaroslav Brustinov
239823b8294SYaroslav Brustinov    def __init__(self, plugins=(), proxyClass=None):
240823b8294SYaroslav Brustinov        self._plugins = []
241823b8294SYaroslav Brustinov        self._extraplugins = ()
242823b8294SYaroslav Brustinov        self._proxies = {}
243823b8294SYaroslav Brustinov        if plugins:
244823b8294SYaroslav Brustinov            self.addPlugins(plugins)
245823b8294SYaroslav Brustinov        if proxyClass is not None:
246823b8294SYaroslav Brustinov            self.proxyClass = proxyClass
247823b8294SYaroslav Brustinov
248823b8294SYaroslav Brustinov    def __getattr__(self, call):
249823b8294SYaroslav Brustinov        try:
250823b8294SYaroslav Brustinov            return self._proxies[call]
251823b8294SYaroslav Brustinov        except KeyError:
252823b8294SYaroslav Brustinov            proxy = self.proxyClass(call, self._plugins)
253823b8294SYaroslav Brustinov            self._proxies[call] = proxy
254823b8294SYaroslav Brustinov        return proxy
255823b8294SYaroslav Brustinov
256823b8294SYaroslav Brustinov    def __iter__(self):
257823b8294SYaroslav Brustinov        return iter(self.plugins)
258823b8294SYaroslav Brustinov
259823b8294SYaroslav Brustinov    def addPlugin(self, plug):
260823b8294SYaroslav Brustinov        # allow, for instance, plugins loaded via entry points to
261823b8294SYaroslav Brustinov        # supplant builtin plugins.
262823b8294SYaroslav Brustinov        new_name = getattr(plug, 'name', object())
263823b8294SYaroslav Brustinov        self._plugins[:] = [p for p in self._plugins
264823b8294SYaroslav Brustinov                            if getattr(p, 'name', None) != new_name]
265823b8294SYaroslav Brustinov        self._plugins.append(plug)
266823b8294SYaroslav Brustinov
267823b8294SYaroslav Brustinov    def addPlugins(self, plugins=(), extraplugins=()):
268823b8294SYaroslav Brustinov        """extraplugins are maintained in a separate list and
269823b8294SYaroslav Brustinov        re-added by loadPlugins() to prevent their being overwritten
270823b8294SYaroslav Brustinov        by plugins added by a subclass of PluginManager
271823b8294SYaroslav Brustinov        """
272823b8294SYaroslav Brustinov        self._extraplugins = extraplugins
273823b8294SYaroslav Brustinov        for plug in iterchain(plugins, extraplugins):
274823b8294SYaroslav Brustinov            self.addPlugin(plug)
275823b8294SYaroslav Brustinov
276823b8294SYaroslav Brustinov    def configure(self, options, config):
277823b8294SYaroslav Brustinov        """Configure the set of plugins with the given options
278823b8294SYaroslav Brustinov        and config instance. After configuration, disabled plugins
279823b8294SYaroslav Brustinov        are removed from the plugins list.
280823b8294SYaroslav Brustinov        """
281823b8294SYaroslav Brustinov        log.debug("Configuring plugins")
282823b8294SYaroslav Brustinov        self.config = config
283823b8294SYaroslav Brustinov        cfg = PluginProxy('configure', self._plugins)
284823b8294SYaroslav Brustinov        cfg(options, config)
285823b8294SYaroslav Brustinov        enabled = [plug for plug in self._plugins if plug.enabled]
286823b8294SYaroslav Brustinov        self.plugins = enabled
287823b8294SYaroslav Brustinov        self.sort()
288823b8294SYaroslav Brustinov        log.debug("Plugins enabled: %s", enabled)
289823b8294SYaroslav Brustinov
290823b8294SYaroslav Brustinov    def loadPlugins(self):
291823b8294SYaroslav Brustinov        for plug in self._extraplugins:
292823b8294SYaroslav Brustinov            self.addPlugin(plug)
293823b8294SYaroslav Brustinov
294823b8294SYaroslav Brustinov    def sort(self):
295823b8294SYaroslav Brustinov        return sort_list(self._plugins, lambda x: getattr(x, 'score', 1), reverse=True)
296823b8294SYaroslav Brustinov
297823b8294SYaroslav Brustinov    def _get_plugins(self):
298823b8294SYaroslav Brustinov        return self._plugins
299823b8294SYaroslav Brustinov
300823b8294SYaroslav Brustinov    def _set_plugins(self, plugins):
301823b8294SYaroslav Brustinov        self._plugins = []
302823b8294SYaroslav Brustinov        self.addPlugins(plugins)
303823b8294SYaroslav Brustinov
304823b8294SYaroslav Brustinov    plugins = property(_get_plugins, _set_plugins, None,
305823b8294SYaroslav Brustinov                       """Access the list of plugins managed by
306823b8294SYaroslav Brustinov                       this plugin manager""")
307823b8294SYaroslav Brustinov
308823b8294SYaroslav Brustinov
309823b8294SYaroslav Brustinovclass ZeroNinePlugin:
310823b8294SYaroslav Brustinov    """Proxy for 0.9 plugins, adapts 0.10 calls to 0.9 standard.
311823b8294SYaroslav Brustinov    """
312823b8294SYaroslav Brustinov    def __init__(self, plugin):
313823b8294SYaroslav Brustinov        self.plugin = plugin
314823b8294SYaroslav Brustinov
315823b8294SYaroslav Brustinov    def options(self, parser, env=os.environ):
316823b8294SYaroslav Brustinov        self.plugin.add_options(parser, env)
317823b8294SYaroslav Brustinov
318823b8294SYaroslav Brustinov    def addError(self, test, err):
319823b8294SYaroslav Brustinov        if not hasattr(self.plugin, 'addError'):
320823b8294SYaroslav Brustinov            return
321823b8294SYaroslav Brustinov        # switch off to addSkip, addDeprecated if those types
322823b8294SYaroslav Brustinov        from nose.exc import SkipTest, DeprecatedTest
323823b8294SYaroslav Brustinov        ec, ev, tb = err
324823b8294SYaroslav Brustinov        if issubclass(ec, SkipTest):
325823b8294SYaroslav Brustinov            if not hasattr(self.plugin, 'addSkip'):
326823b8294SYaroslav Brustinov                return
327823b8294SYaroslav Brustinov            return self.plugin.addSkip(test.test)
328823b8294SYaroslav Brustinov        elif issubclass(ec, DeprecatedTest):
329823b8294SYaroslav Brustinov            if not hasattr(self.plugin, 'addDeprecated'):
330823b8294SYaroslav Brustinov                return
331823b8294SYaroslav Brustinov            return self.plugin.addDeprecated(test.test)
332823b8294SYaroslav Brustinov        # add capt
333823b8294SYaroslav Brustinov        capt = test.capturedOutput
334823b8294SYaroslav Brustinov        return self.plugin.addError(test.test, err, capt)
335823b8294SYaroslav Brustinov
336823b8294SYaroslav Brustinov    def loadTestsFromFile(self, filename):
337823b8294SYaroslav Brustinov        if hasattr(self.plugin, 'loadTestsFromPath'):
338823b8294SYaroslav Brustinov            return self.plugin.loadTestsFromPath(filename)
339823b8294SYaroslav Brustinov
340823b8294SYaroslav Brustinov    def addFailure(self, test, err):
341823b8294SYaroslav Brustinov        if not hasattr(self.plugin, 'addFailure'):
342823b8294SYaroslav Brustinov            return
343823b8294SYaroslav Brustinov        # add capt and tbinfo
344823b8294SYaroslav Brustinov        capt = test.capturedOutput
345823b8294SYaroslav Brustinov        tbinfo = test.tbinfo
346823b8294SYaroslav Brustinov        return self.plugin.addFailure(test.test, err, capt, tbinfo)
347823b8294SYaroslav Brustinov
348823b8294SYaroslav Brustinov    def addSuccess(self, test):
349823b8294SYaroslav Brustinov        if not hasattr(self.plugin, 'addSuccess'):
350823b8294SYaroslav Brustinov            return
351823b8294SYaroslav Brustinov        capt = test.capturedOutput
352823b8294SYaroslav Brustinov        self.plugin.addSuccess(test.test, capt)
353823b8294SYaroslav Brustinov
354823b8294SYaroslav Brustinov    def startTest(self, test):
355823b8294SYaroslav Brustinov        if not hasattr(self.plugin, 'startTest'):
356823b8294SYaroslav Brustinov            return
357823b8294SYaroslav Brustinov        return self.plugin.startTest(test.test)
358823b8294SYaroslav Brustinov
359823b8294SYaroslav Brustinov    def stopTest(self, test):
360823b8294SYaroslav Brustinov        if not hasattr(self.plugin, 'stopTest'):
361823b8294SYaroslav Brustinov            return
362823b8294SYaroslav Brustinov        return self.plugin.stopTest(test.test)
363823b8294SYaroslav Brustinov
364823b8294SYaroslav Brustinov    def __getattr__(self, val):
365823b8294SYaroslav Brustinov        return getattr(self.plugin, val)
366823b8294SYaroslav Brustinov
367823b8294SYaroslav Brustinov
368823b8294SYaroslav Brustinovclass EntryPointPluginManager(PluginManager):
369823b8294SYaroslav Brustinov    """Plugin manager that loads plugins from the `nose.plugins` and
370823b8294SYaroslav Brustinov    `nose.plugins.0.10` entry points.
371823b8294SYaroslav Brustinov    """
372823b8294SYaroslav Brustinov    entry_points = (('nose.plugins.0.10', None),
373823b8294SYaroslav Brustinov                    ('nose.plugins', ZeroNinePlugin))
374823b8294SYaroslav Brustinov
375823b8294SYaroslav Brustinov    def loadPlugins(self):
376823b8294SYaroslav Brustinov        """Load plugins by iterating the `nose.plugins` entry point.
377823b8294SYaroslav Brustinov        """
378823b8294SYaroslav Brustinov        from pkg_resources import iter_entry_points
379823b8294SYaroslav Brustinov        loaded = {}
380823b8294SYaroslav Brustinov        for entry_point, adapt in self.entry_points:
381823b8294SYaroslav Brustinov            for ep in iter_entry_points(entry_point):
382823b8294SYaroslav Brustinov                if ep.name in loaded:
383823b8294SYaroslav Brustinov                    continue
384823b8294SYaroslav Brustinov                loaded[ep.name] = True
385823b8294SYaroslav Brustinov                log.debug('%s load plugin %s', self.__class__.__name__, ep)
386823b8294SYaroslav Brustinov                try:
387823b8294SYaroslav Brustinov                    plugcls = ep.load()
388823b8294SYaroslav Brustinov                except KeyboardInterrupt:
389823b8294SYaroslav Brustinov                    raise
390823b8294SYaroslav Brustinov                except Exception, e:
391823b8294SYaroslav Brustinov                    # never want a plugin load to kill the test run
392823b8294SYaroslav Brustinov                    # but we can't log here because the logger is not yet
393823b8294SYaroslav Brustinov                    # configured
394823b8294SYaroslav Brustinov                    warn("Unable to load plugin %s: %s" % (ep, e),
395823b8294SYaroslav Brustinov                         RuntimeWarning)
396823b8294SYaroslav Brustinov                    continue
397823b8294SYaroslav Brustinov                if adapt:
398823b8294SYaroslav Brustinov                    plug = adapt(plugcls())
399823b8294SYaroslav Brustinov                else:
400823b8294SYaroslav Brustinov                    plug = plugcls()
401823b8294SYaroslav Brustinov                self.addPlugin(plug)
402823b8294SYaroslav Brustinov        super(EntryPointPluginManager, self).loadPlugins()
403823b8294SYaroslav Brustinov
404823b8294SYaroslav Brustinov
405823b8294SYaroslav Brustinovclass BuiltinPluginManager(PluginManager):
406823b8294SYaroslav Brustinov    """Plugin manager that loads plugins from the list in
407823b8294SYaroslav Brustinov    `nose.plugins.builtin`.
408823b8294SYaroslav Brustinov    """
409823b8294SYaroslav Brustinov    def loadPlugins(self):
410823b8294SYaroslav Brustinov        """Load plugins in nose.plugins.builtin
411823b8294SYaroslav Brustinov        """
412823b8294SYaroslav Brustinov        from nose.plugins import builtin
413823b8294SYaroslav Brustinov        for plug in builtin.plugins:
414823b8294SYaroslav Brustinov            self.addPlugin(plug())
415823b8294SYaroslav Brustinov        super(BuiltinPluginManager, self).loadPlugins()
416823b8294SYaroslav Brustinov
417823b8294SYaroslav Brustinovtry:
418823b8294SYaroslav Brustinov    import pkg_resources
419823b8294SYaroslav Brustinov    class DefaultPluginManager(EntryPointPluginManager, BuiltinPluginManager):
420823b8294SYaroslav Brustinov        pass
421823b8294SYaroslav Brustinov
422823b8294SYaroslav Brustinovexcept ImportError:
423823b8294SYaroslav Brustinov    class DefaultPluginManager(BuiltinPluginManager):
424823b8294SYaroslav Brustinov        pass
425823b8294SYaroslav Brustinov
426823b8294SYaroslav Brustinovclass RestrictedPluginManager(DefaultPluginManager):
427823b8294SYaroslav Brustinov    """Plugin manager that restricts the plugin list to those not
428823b8294SYaroslav Brustinov    excluded by a list of exclude methods. Any plugin that implements
429823b8294SYaroslav Brustinov    an excluded method will be removed from the manager's plugin list
430823b8294SYaroslav Brustinov    after plugins are loaded.
431823b8294SYaroslav Brustinov    """
432823b8294SYaroslav Brustinov    def __init__(self, plugins=(), exclude=(), load=True):
433823b8294SYaroslav Brustinov        DefaultPluginManager.__init__(self, plugins)
434823b8294SYaroslav Brustinov        self.load = load
435823b8294SYaroslav Brustinov        self.exclude = exclude
436823b8294SYaroslav Brustinov        self.excluded = []
437823b8294SYaroslav Brustinov        self._excludedOpts = None
438823b8294SYaroslav Brustinov
439823b8294SYaroslav Brustinov    def excludedOption(self, name):
440823b8294SYaroslav Brustinov        if self._excludedOpts is None:
441823b8294SYaroslav Brustinov            from optparse import OptionParser
442823b8294SYaroslav Brustinov            self._excludedOpts = OptionParser(add_help_option=False)
443823b8294SYaroslav Brustinov            for plugin in self.excluded:
444823b8294SYaroslav Brustinov                plugin.options(self._excludedOpts, env={})
445823b8294SYaroslav Brustinov        return self._excludedOpts.get_option('--' + name)
446823b8294SYaroslav Brustinov
447823b8294SYaroslav Brustinov    def loadPlugins(self):
448823b8294SYaroslav Brustinov        if self.load:
449823b8294SYaroslav Brustinov            DefaultPluginManager.loadPlugins(self)
450823b8294SYaroslav Brustinov        allow = []
451823b8294SYaroslav Brustinov        for plugin in self.plugins:
452823b8294SYaroslav Brustinov            ok = True
453823b8294SYaroslav Brustinov            for method in self.exclude:
454823b8294SYaroslav Brustinov                if hasattr(plugin, method):
455823b8294SYaroslav Brustinov                    ok = False
456823b8294SYaroslav Brustinov                    self.excluded.append(plugin)
457823b8294SYaroslav Brustinov                    break
458823b8294SYaroslav Brustinov            if ok:
459823b8294SYaroslav Brustinov                allow.append(plugin)
460823b8294SYaroslav Brustinov        self.plugins = allow