simplify pluginmanager, move plugin validation code to plugin, remove unused code
--HG-- branch : trunk
This commit is contained in:
		
							parent
							
								
									0361b73d75
								
							
						
					
					
						commit
						8737254a74
					
				| 
						 | 
					@ -44,8 +44,7 @@ class PluginManager(object):
 | 
				
			||||||
        if name in self._name2plugin:
 | 
					        if name in self._name2plugin:
 | 
				
			||||||
            return False
 | 
					            return False
 | 
				
			||||||
        self._name2plugin[name] = plugin
 | 
					        self._name2plugin[name] = plugin
 | 
				
			||||||
        self.hook.pytest_plugin_registered(plugin=plugin)
 | 
					        self.hook.pytest_plugin_registered(manager=self, plugin=plugin)
 | 
				
			||||||
        self._checkplugin(plugin)
 | 
					 | 
				
			||||||
        self.comregistry.register(plugin)
 | 
					        self.comregistry.register(plugin)
 | 
				
			||||||
        return True
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -138,46 +137,6 @@ class PluginManager(object):
 | 
				
			||||||
    def _warn(self, msg):
 | 
					    def _warn(self, msg):
 | 
				
			||||||
        print ("===WARNING=== %s" % (msg,))
 | 
					        print ("===WARNING=== %s" % (msg,))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _checkplugin(self, plugin):
 | 
					 | 
				
			||||||
        # =====================================================
 | 
					 | 
				
			||||||
        # check plugin hooks 
 | 
					 | 
				
			||||||
        # =====================================================
 | 
					 | 
				
			||||||
        methods = collectattr(plugin)
 | 
					 | 
				
			||||||
        hooks = collectattr(hookspec)
 | 
					 | 
				
			||||||
        stringio = py.io.TextIO()
 | 
					 | 
				
			||||||
        def Print(*args):
 | 
					 | 
				
			||||||
            if args:
 | 
					 | 
				
			||||||
                stringio.write(" ".join(map(str, args)))
 | 
					 | 
				
			||||||
            stringio.write("\n")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        fail = False
 | 
					 | 
				
			||||||
        while methods:
 | 
					 | 
				
			||||||
            name, method = methods.popitem()
 | 
					 | 
				
			||||||
            #print "checking", name
 | 
					 | 
				
			||||||
            if isgenerichook(name):
 | 
					 | 
				
			||||||
                continue
 | 
					 | 
				
			||||||
            if name not in hooks: 
 | 
					 | 
				
			||||||
                Print("found unknown hook:", name)
 | 
					 | 
				
			||||||
                fail = True
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                method_args = getargs(method)
 | 
					 | 
				
			||||||
                if '__multicall__' in method_args:
 | 
					 | 
				
			||||||
                    method_args.remove('__multicall__')
 | 
					 | 
				
			||||||
                hook = hooks[name]
 | 
					 | 
				
			||||||
                hookargs = getargs(hook)
 | 
					 | 
				
			||||||
                for arg in method_args:
 | 
					 | 
				
			||||||
                    if arg not in hookargs:
 | 
					 | 
				
			||||||
                        Print("argument %r not available"  %(arg, ))
 | 
					 | 
				
			||||||
                        Print("actual definition: %s" %(formatdef(method)))
 | 
					 | 
				
			||||||
                        Print("available hook arguments: %s" % 
 | 
					 | 
				
			||||||
                                ", ".join(hookargs))
 | 
					 | 
				
			||||||
                        fail = True
 | 
					 | 
				
			||||||
                        break 
 | 
					 | 
				
			||||||
                #if not fail:
 | 
					 | 
				
			||||||
                #    print "matching hook:", formatdef(method)
 | 
					 | 
				
			||||||
            if fail:
 | 
					 | 
				
			||||||
                name = getattr(plugin, '__name__', plugin)
 | 
					 | 
				
			||||||
                raise self.Error("%s:\n%s" %(name, stringio.getvalue()))
 | 
					 | 
				
			||||||
    # 
 | 
					    # 
 | 
				
			||||||
    #
 | 
					    #
 | 
				
			||||||
    # API for interacting with registered and instantiated plugin objects 
 | 
					    # API for interacting with registered and instantiated plugin objects 
 | 
				
			||||||
| 
						 | 
					@ -224,9 +183,6 @@ class PluginManager(object):
 | 
				
			||||||
        config.hook.pytest_unconfigure(config=config)
 | 
					        config.hook.pytest_unconfigure(config=config)
 | 
				
			||||||
        config.pluginmanager.unregister(self)
 | 
					        config.pluginmanager.unregister(self)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# 
 | 
					 | 
				
			||||||
#  XXX old code to automatically load classes
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
def canonical_importname(name):
 | 
					def canonical_importname(name):
 | 
				
			||||||
    name = name.lower()
 | 
					    name = name.lower()
 | 
				
			||||||
    modprefix = "pytest_"
 | 
					    modprefix = "pytest_"
 | 
				
			||||||
| 
						 | 
					@ -254,59 +210,3 @@ def importplugin(importspec):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def isgenerichook(name):
 | 
					 | 
				
			||||||
    return name == "pytest_plugins" or \
 | 
					 | 
				
			||||||
           name.startswith("pytest_funcarg__")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def getargs(func):
 | 
					 | 
				
			||||||
    args = py.std.inspect.getargs(py.code.getrawcode(func))[0]
 | 
					 | 
				
			||||||
    startindex = py.std.inspect.ismethod(func) and 1 or 0
 | 
					 | 
				
			||||||
    return args[startindex:]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def collectattr(obj, prefixes=("pytest_",)):
 | 
					 | 
				
			||||||
    methods = {}
 | 
					 | 
				
			||||||
    for apiname in dir(obj):
 | 
					 | 
				
			||||||
        for prefix in prefixes:
 | 
					 | 
				
			||||||
            if apiname.startswith(prefix):
 | 
					 | 
				
			||||||
                methods[apiname] = getattr(obj, apiname) 
 | 
					 | 
				
			||||||
    return methods 
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def formatdef(func):
 | 
					 | 
				
			||||||
    return "%s%s" %(
 | 
					 | 
				
			||||||
        func.__name__, 
 | 
					 | 
				
			||||||
        py.std.inspect.formatargspec(*py.std.inspect.getargspec(func))
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__":
 | 
					 | 
				
			||||||
    import py.plugin
 | 
					 | 
				
			||||||
    basedir = py._dir.join('_plugin')
 | 
					 | 
				
			||||||
    name2text = {}
 | 
					 | 
				
			||||||
    for p in basedir.listdir("pytest_*"):
 | 
					 | 
				
			||||||
        if p.ext == ".py" or (
 | 
					 | 
				
			||||||
           p.check(dir=1) and p.join("__init__.py").check()):
 | 
					 | 
				
			||||||
            impname = p.purebasename 
 | 
					 | 
				
			||||||
            if impname.find("__") != -1:
 | 
					 | 
				
			||||||
                continue
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                plugin = importplugin(impname)
 | 
					 | 
				
			||||||
            except (ImportError, py.impl.test.outcome.Skipped):
 | 
					 | 
				
			||||||
                name2text[impname] = "IMPORT ERROR"
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                doc = plugin.__doc__ or ""
 | 
					 | 
				
			||||||
                doc = doc.strip()
 | 
					 | 
				
			||||||
                name2text[impname] = doc
 | 
					 | 
				
			||||||
           
 | 
					 | 
				
			||||||
    for name in sorted(name2text.keys()):
 | 
					 | 
				
			||||||
        text = name2text[name]
 | 
					 | 
				
			||||||
        if name[0] == "_":
 | 
					 | 
				
			||||||
            continue
 | 
					 | 
				
			||||||
        print ("%-20s %s" % (name, text.split("\n")[0]))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        #text = py.std.textwrap.wrap(name2text[name], 
 | 
					 | 
				
			||||||
        #    width = 80,
 | 
					 | 
				
			||||||
        #    initial_indent="%s: " % name, 
 | 
					 | 
				
			||||||
        #    replace_whitespace = False)
 | 
					 | 
				
			||||||
        #for line in text:
 | 
					 | 
				
			||||||
        #    print line
 | 
					 | 
				
			||||||
     
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -159,7 +159,7 @@ def pytest_looponfailinfo(failreports, rootdirs):
 | 
				
			||||||
# error handling and internal debugging hooks 
 | 
					# error handling and internal debugging hooks 
 | 
				
			||||||
# -------------------------------------------------------------------------
 | 
					# -------------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def pytest_plugin_registered(plugin):
 | 
					def pytest_plugin_registered(plugin, manager):
 | 
				
			||||||
    """ a new py lib plugin got registered. """
 | 
					    """ a new py lib plugin got registered. """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def pytest_plugin_unregistered(plugin):
 | 
					def pytest_plugin_unregistered(plugin):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
""" provide version info, conftest/environment config names. 
 | 
					""" provide version info, conftest/environment config names. 
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
import py
 | 
					import py
 | 
				
			||||||
import sys
 | 
					import inspect, sys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def pytest_addoption(parser):
 | 
					def pytest_addoption(parser):
 | 
				
			||||||
    group = parser.getgroup('debugconfig')
 | 
					    group = parser.getgroup('debugconfig')
 | 
				
			||||||
| 
						 | 
					@ -61,3 +61,73 @@ conftest_options = (
 | 
				
			||||||
    ('collect_ignore', '(relative) paths ignored during collection'), 
 | 
					    ('collect_ignore', '(relative) paths ignored during collection'), 
 | 
				
			||||||
    ('rsyncdirs', 'to-be-rsynced directories for dist-testing'), 
 | 
					    ('rsyncdirs', 'to-be-rsynced directories for dist-testing'), 
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# =====================================================
 | 
				
			||||||
 | 
					# validate plugin syntax and hooks 
 | 
				
			||||||
 | 
					# =====================================================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def pytest_plugin_registered(manager, plugin):
 | 
				
			||||||
 | 
					    hookspec = manager.hook._hookspecs
 | 
				
			||||||
 | 
					    methods = collectattr(plugin)
 | 
				
			||||||
 | 
					    hooks = collectattr(hookspec)
 | 
				
			||||||
 | 
					    stringio = py.io.TextIO()
 | 
				
			||||||
 | 
					    def Print(*args):
 | 
				
			||||||
 | 
					        if args:
 | 
				
			||||||
 | 
					            stringio.write(" ".join(map(str, args)))
 | 
				
			||||||
 | 
					        stringio.write("\n")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fail = False
 | 
				
			||||||
 | 
					    while methods:
 | 
				
			||||||
 | 
					        name, method = methods.popitem()
 | 
				
			||||||
 | 
					        #print "checking", name
 | 
				
			||||||
 | 
					        if isgenerichook(name):
 | 
				
			||||||
 | 
					            continue
 | 
				
			||||||
 | 
					        if name not in hooks: 
 | 
				
			||||||
 | 
					            Print("found unknown hook:", name)
 | 
				
			||||||
 | 
					            fail = True
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            method_args = getargs(method)
 | 
				
			||||||
 | 
					            if '__multicall__' in method_args:
 | 
				
			||||||
 | 
					                method_args.remove('__multicall__')
 | 
				
			||||||
 | 
					            hook = hooks[name]
 | 
				
			||||||
 | 
					            hookargs = getargs(hook)
 | 
				
			||||||
 | 
					            for arg in method_args:
 | 
				
			||||||
 | 
					                if arg not in hookargs:
 | 
				
			||||||
 | 
					                    Print("argument %r not available"  %(arg, ))
 | 
				
			||||||
 | 
					                    Print("actual definition: %s" %(formatdef(method)))
 | 
				
			||||||
 | 
					                    Print("available hook arguments: %s" % 
 | 
				
			||||||
 | 
					                            ", ".join(hookargs))
 | 
				
			||||||
 | 
					                    fail = True
 | 
				
			||||||
 | 
					                    break 
 | 
				
			||||||
 | 
					            #if not fail:
 | 
				
			||||||
 | 
					            #    print "matching hook:", formatdef(method)
 | 
				
			||||||
 | 
					        if fail:
 | 
				
			||||||
 | 
					            name = getattr(plugin, '__name__', plugin)
 | 
				
			||||||
 | 
					            raise PluginValidationError("%s:\n%s" %(name, stringio.getvalue()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PluginValidationError(Exception):
 | 
				
			||||||
 | 
					    """ plugin failed validation. """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def isgenerichook(name):
 | 
				
			||||||
 | 
					    return name == "pytest_plugins" or \
 | 
				
			||||||
 | 
					           name.startswith("pytest_funcarg__")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def getargs(func):
 | 
				
			||||||
 | 
					    args = inspect.getargs(py.code.getrawcode(func))[0]
 | 
				
			||||||
 | 
					    startindex = inspect.ismethod(func) and 1 or 0
 | 
				
			||||||
 | 
					    return args[startindex:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def collectattr(obj, prefixes=("pytest_",)):
 | 
				
			||||||
 | 
					    methods = {}
 | 
				
			||||||
 | 
					    for apiname in dir(obj):
 | 
				
			||||||
 | 
					        for prefix in prefixes:
 | 
				
			||||||
 | 
					            if apiname.startswith(prefix):
 | 
				
			||||||
 | 
					                methods[apiname] = getattr(obj, apiname) 
 | 
				
			||||||
 | 
					    return methods 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def formatdef(func):
 | 
				
			||||||
 | 
					    return "%s%s" %(
 | 
				
			||||||
 | 
					        func.__name__, 
 | 
				
			||||||
 | 
					        inspect.formatargspec(*inspect.getargspec(func))
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,5 @@
 | 
				
			||||||
import py, os
 | 
					import py, os
 | 
				
			||||||
 | 
					from py.plugin.pytest_helpconfig import collectattr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_version(testdir):
 | 
					def test_version(testdir):
 | 
				
			||||||
    assert py.version == py.__version__ 
 | 
					    assert py.version == py.__version__ 
 | 
				
			||||||
| 
						 | 
					@ -16,3 +17,15 @@ def test_helpconfig(testdir):
 | 
				
			||||||
        "*cmdline*conftest*ENV*",
 | 
					        "*cmdline*conftest*ENV*",
 | 
				
			||||||
    ])
 | 
					    ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_collectattr():
 | 
				
			||||||
 | 
					    class A:
 | 
				
			||||||
 | 
					        def pytest_hello(self):
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					    class B(A):
 | 
				
			||||||
 | 
					        def pytest_world(self):
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					    methods = py.builtin.sorted(collectattr(B))
 | 
				
			||||||
 | 
					    assert list(methods) == ['pytest_hello', 'pytest_world']
 | 
				
			||||||
 | 
					    methods = py.builtin.sorted(collectattr(B()))
 | 
				
			||||||
 | 
					    assert list(methods) == ['pytest_hello', 'pytest_world']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
import py, os
 | 
					import py, os
 | 
				
			||||||
from py.impl.test.pluginmanager import PluginManager, canonical_importname, collectattr
 | 
					from py.impl.test.pluginmanager import PluginManager, canonical_importname
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestBootstrapping:
 | 
					class TestBootstrapping:
 | 
				
			||||||
    def test_consider_env_fails_to_import(self, monkeypatch):
 | 
					    def test_consider_env_fails_to_import(self, monkeypatch):
 | 
				
			||||||
| 
						 | 
					@ -185,14 +185,14 @@ class TestBootstrapping:
 | 
				
			||||||
        class hello:
 | 
					        class hello:
 | 
				
			||||||
            def pytest_gurgel(self):
 | 
					            def pytest_gurgel(self):
 | 
				
			||||||
                pass
 | 
					                pass
 | 
				
			||||||
        py.test.raises(pp.Error, "pp.register(hello())")
 | 
					        py.test.raises(Exception, "pp.register(hello())")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_register_mismatch_arg(self):
 | 
					    def test_register_mismatch_arg(self):
 | 
				
			||||||
        pp = PluginManager()
 | 
					        pp = PluginManager()
 | 
				
			||||||
        class hello:
 | 
					        class hello:
 | 
				
			||||||
            def pytest_configure(self, asd):
 | 
					            def pytest_configure(self, asd):
 | 
				
			||||||
                pass
 | 
					                pass
 | 
				
			||||||
        excinfo = py.test.raises(pp.Error, "pp.register(hello())")
 | 
					        excinfo = py.test.raises(Exception, "pp.register(hello())")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_canonical_importname(self):
 | 
					    def test_canonical_importname(self):
 | 
				
			||||||
        for name in 'xyz', 'pytest_xyz', 'pytest_Xyz', 'Xyz':
 | 
					        for name in 'xyz', 'pytest_xyz', 'pytest_Xyz', 'Xyz':
 | 
				
			||||||
| 
						 | 
					@ -270,18 +270,6 @@ class TestPytestPluginInteractions:
 | 
				
			||||||
        assert not pluginmanager.listattr("hello")
 | 
					        assert not pluginmanager.listattr("hello")
 | 
				
			||||||
        assert pluginmanager.listattr("x") == [42]
 | 
					        assert pluginmanager.listattr("x") == [42]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_collectattr():
 | 
					 | 
				
			||||||
    class A:
 | 
					 | 
				
			||||||
        def pytest_hello(self):
 | 
					 | 
				
			||||||
            pass
 | 
					 | 
				
			||||||
    class B(A):
 | 
					 | 
				
			||||||
        def pytest_world(self):
 | 
					 | 
				
			||||||
            pass
 | 
					 | 
				
			||||||
    methods = py.builtin.sorted(collectattr(B))
 | 
					 | 
				
			||||||
    assert list(methods) == ['pytest_hello', 'pytest_world']
 | 
					 | 
				
			||||||
    methods = py.builtin.sorted(collectattr(B()))
 | 
					 | 
				
			||||||
    assert list(methods) == ['pytest_hello', 'pytest_world']
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@py.test.mark.xfail
 | 
					@py.test.mark.xfail
 | 
				
			||||||
def test_namespace_has_default_and_env_plugins(testdir):
 | 
					def test_namespace_has_default_and_env_plugins(testdir):
 | 
				
			||||||
    p = testdir.makepyfile("""
 | 
					    p = testdir.makepyfile("""
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue