integrate plugin hook checking directly when registering

remove plugintester plugin, all functionality now in testdir

--HG--
branch : trunk
This commit is contained in:
holger krekel 2009-05-22 23:50:35 +02:00
parent db2ef3e9e8
commit a93918a480
24 changed files with 106 additions and 183 deletions

View File

@ -327,22 +327,3 @@ class CoveragePlugin:
self.coverage.start() self.coverage.start()
# ===============================================================================
# plugin tests
# ===============================================================================
# XXX
'''
def test_generic(plugintester):
plugintester.apicheck(EventlogPlugin)
testdir = plugintester.testdir()
testdir.makepyfile("""
def test_pass():
pass
""")
testdir.runpytest("--eventlog=event.log")
s = testdir.tmpdir.join("event.log").read()
assert s.find("TestrunStart") != -1
assert s.find("ItemTestReport") != -1
assert s.find("TestrunFinish") != -1
'''

View File

@ -115,10 +115,7 @@ gr_tests = greenlet.getcurrent()
# plugin tests # plugin tests
# =============================================================================== # ===============================================================================
def test_generic(plugintester): def test_generic(testdir):
plugintester.apicheck(TwistedPlugin)
testdir = plugintester.testdir()
testdir.makepyfile(''' testdir.makepyfile('''
def test_pass(): def test_pass():
pass pass

View File

@ -1,6 +1,6 @@
import py import py
pytest_plugins = "pytester", "plugintester" pytest_plugins = "pytester"
def pytest_collect_file(path, parent): def pytest_collect_file(path, parent):
if path.basename.startswith("pytest_") and path.ext == ".py": if path.basename.startswith("pytest_") and path.ext == ".py":

View File

@ -99,9 +99,6 @@ class HookRecorder:
return l[0] return l[0]
def test_generic(plugintester):
plugintester.hookcheck()
def test_hookrecorder_basic(): def test_hookrecorder_basic():
comregistry = py._com.Registry() comregistry = py._com.Registry()
rec = HookRecorder(comregistry) rec = HookRecorder(comregistry)

View File

@ -172,9 +172,6 @@ def test_implied_different_sessions(tmpdir):
assert x('-n3') == 'DSession' assert x('-n3') == 'DSession'
assert x('-f') == 'LooponfailingSession' assert x('-f') == 'LooponfailingSession'
def test_generic(plugintester):
plugintester.hookcheck()
def test_plugin_specify(testdir): def test_plugin_specify(testdir):
testdir.chdir() testdir.chdir()
config = py.test.raises(ImportError, """ config = py.test.raises(ImportError, """

View File

@ -156,8 +156,3 @@ class TestDoctests:
" 1", " 1",
"*test_txtfile_failing.txt:2: DocTestFailure" "*test_txtfile_failing.txt:2: DocTestFailure"
]) ])
def test_generic(plugintester):
plugintester.hookcheck()

View File

@ -38,10 +38,6 @@ class Execnetcleanup:
self._gateways[-1].exit() self._gateways[-1].exit()
return res return res
def test_generic(plugintester):
plugintester.hookcheck(cls=Execnetcleanup)
plugintester.hookcheck()
@py.test.mark.xfail("clarify plugin registration/unregistration") @py.test.mark.xfail("clarify plugin registration/unregistration")
def test_execnetplugin(testdir): def test_execnetplugin(testdir):
p = ExecnetcleanupPlugin() p = ExecnetcleanupPlugin()

View File

@ -50,9 +50,6 @@ def get_coverage(datafile, config):
return coverage return coverage
def test_generic(plugintester):
plugintester.hookcheck()
def test_functional(testdir): def test_functional(testdir):
py.test.importorskip("figleaf") py.test.importorskip("figleaf")
testdir.plugins.append("figleaf") testdir.plugins.append("figleaf")

View File

@ -21,9 +21,6 @@ def pytest_unconfigure(config):
# plugin tests # plugin tests
# =============================================================================== # ===============================================================================
def test_generic(plugintester):
plugintester.hookcheck()
def test_functional(testdir): def test_functional(testdir):
testdir.makepyfile(""" testdir.makepyfile("""
def test_pass(): def test_pass():

View File

@ -35,9 +35,6 @@ class Capture:
self._capture = self._captureclass() self._capture = self._captureclass()
return res return res
def test_generic(plugintester):
plugintester.hookcheck()
class TestCapture: class TestCapture:
def test_std_functional(self, testdir): def test_std_functional(self, testdir):
reprec = testdir.inline_runsource(""" reprec = testdir.inline_runsource("""

View File

@ -1,94 +0,0 @@
"""
plugin with support classes and functions for testing pytest functionality
"""
import py
from py.__.test.plugin import api
def pytest_funcarg__plugintester(request):
return PluginTester(request)
class PluginTester:
def __init__(self, request):
self.request = request
def testdir(self, globs=None):
from pytest_pytester import TmpTestdir
testdir = TmpTestdir(self.request)
self.request.addfinalizer(testdir.finalize)
if globs is None:
globs = py.std.sys._getframe(-1).f_globals
testdir.plugins.append(globs)
#
#for colitem in self.request.listchain():
# if isinstance(colitem, py.test.collect.Module) and \
# colitem.name.startswith("pytest_"):
# crunner.plugins.append(colitem.fspath.purebasename)
# break
return testdir
def hookcheck(self, name=None, cls=None):
if cls is None:
if name is None:
name = py.std.sys._getframe(-1).f_globals['__name__']
plugin = __import__(name)
else:
plugin = cls
print "checking", plugin
fail = False
pm = py.test._PluginManager()
methods = collectattr(plugin)
hooks = collectattr(api.PluginHooks)
getargs = py.std.inspect.getargs
def isgenerichook(name):
return name.startswith("pytest_funcarg__")
while methods:
name, method = methods.popitem()
if isgenerichook(name):
continue
if name not in hooks:
print "found unknown hook: %s" % name
fail = True
else:
hook = hooks[name]
if not hasattr(hook, 'func_code'):
continue # XXX do some checks on attributes as well?
method_args = getargs(method.func_code)
if '__call__' in method_args[0]:
method_args[0].remove('__call__')
hookargs = getargs(hook.func_code)
for arg, hookarg in zip(method_args[0], hookargs[0]):
if arg != hookarg:
print "argument mismatch:"
print "actual : %s.%s" %(plugin.__name__, formatdef(method))
print "required:", formatdef(hook)
fail = True
break
if not fail:
print "matching hook:", formatdef(method)
if fail:
py.test.fail("Plugin API error")
def collectattr(obj, prefixes=("pytest_",)):
methods = {}
for apiname in vars(obj):
for prefix in prefixes:
if apiname.startswith(prefix):
methods[apiname] = getattr(obj, apiname)
return methods
def formatdef(func):
formatargspec = py.std.inspect.formatargspec
getargspec = py.std.inspect.formatargspec
return "%s%s" %(
func.func_name,
py.std.inspect.formatargspec(*py.std.inspect.getargspec(func))
)
# ===============================================================================
# plugin tests
# ===============================================================================
def test_generic(plugintester):
plugintester.hookcheck()

View File

@ -38,9 +38,6 @@ def pytest_terminal_summary(terminalreporter):
break break
def test_apicheck(plugintester):
plugintester.hookcheck()
def test_toproxy(testdir, monkeypatch): def test_toproxy(testdir, monkeypatch):
l = [] l = []
class MockProxy: class MockProxy:

View File

@ -38,8 +38,4 @@ class PylintItem(py.test.collect.Item):
print ">>>", print ">>>",
print rating print rating
def test_generic(plugintester):
plugintester.hookcheck(PylintPlugin)
#def test_functional <pull from figleaf plugin>

View File

@ -26,9 +26,6 @@ def pytest_funcarg__reportrecorder(request):
request.addfinalizer(lambda: reprec.comregistry.unregister(reprec)) request.addfinalizer(lambda: reprec.comregistry.unregister(reprec))
return reprec return reprec
def test_generic(plugintester):
plugintester.hookcheck()
class RunResult: class RunResult:
def __init__(self, ret, outlines, errlines): def __init__(self, ret, outlines, errlines):
self.ret = ret self.ret = ret

View File

@ -348,8 +348,6 @@ def localrefcheck(tryfn, path, lineno):
# #
# PLUGIN tests # PLUGIN tests
# #
def test_generic(plugintester):
plugintester.hookcheck()
def test_deindent(): def test_deindent():
assert deindent('foo') == 'foo' assert deindent('foo') == 'foo'

View File

@ -300,9 +300,8 @@ class TestWithFunctionIntegration:
archive.init_db() archive.init_db()
return archive return archive
def test_collection_report(self, plugintester): def test_collection_report(self, testdir):
py.test.skip("Needs a rewrite for db version.") py.test.skip("Needs a rewrite for db version.")
testdir = plugintester.testdir()
ok = testdir.makepyfile(test_collection_ok="") ok = testdir.makepyfile(test_collection_ok="")
skip = testdir.makepyfile(test_collection_skip="import py ; py.test.skip('hello')") skip = testdir.makepyfile(test_collection_skip="import py ; py.test.skip('hello')")
fail = testdir.makepyfile(test_collection_fail="XXX") fail = testdir.makepyfile(test_collection_fail="XXX")
@ -360,9 +359,7 @@ class TestWithFunctionIntegration:
assert entry_lines[-1][0] == ' ' assert entry_lines[-1][0] == ' '
assert 'ValueError' in entry assert 'ValueError' in entry
def test_generic(plugintester): def test_generic(testdir):
plugintester.hookcheck()
testdir = plugintester.testdir()
testdir.makepyfile(""" testdir.makepyfile("""
import py import py
def test_pass(): def test_pass():

View File

@ -163,8 +163,7 @@ class TestWithFunctionIntegration:
testdir.runpytest(*args) testdir.runpytest(*args)
return filter(None, resultlog.readlines(cr=0)) return filter(None, resultlog.readlines(cr=0))
def test_collection_report(self, plugintester): def test_collection_report(self, testdir):
testdir = plugintester.testdir()
ok = testdir.makepyfile(test_collection_ok="") ok = testdir.makepyfile(test_collection_ok="")
skip = testdir.makepyfile(test_collection_skip="import py ; py.test.skip('hello')") skip = testdir.makepyfile(test_collection_skip="import py ; py.test.skip('hello')")
fail = testdir.makepyfile(test_collection_fail="XXX") fail = testdir.makepyfile(test_collection_fail="XXX")
@ -186,8 +185,7 @@ class TestWithFunctionIntegration:
assert x.startswith(" ") assert x.startswith(" ")
assert "XXX" in "".join(lines[1:]) assert "XXX" in "".join(lines[1:])
def test_log_test_outcomes(self, plugintester): def test_log_test_outcomes(self, testdir):
testdir = plugintester.testdir()
mod = testdir.makepyfile(test_mod=""" mod = testdir.makepyfile(test_mod="""
import py import py
def test_pass(): pass def test_pass(): pass
@ -224,9 +222,7 @@ class TestWithFunctionIntegration:
assert entry_lines[-1][0] == ' ' assert entry_lines[-1][0] == ' '
assert 'ValueError' in entry assert 'ValueError' in entry
def test_generic(plugintester, LineMatcher): def test_generic(testdir, LineMatcher):
plugintester.hookcheck()
testdir = plugintester.testdir()
testdir.plugins.append("resultlog") testdir.plugins.append("resultlog")
testdir.makepyfile(""" testdir.makepyfile("""
import py import py

View File

@ -126,8 +126,6 @@ class ItemFixtureReport(BaseReport):
# #
# =============================================================================== # ===============================================================================
def test_generic(plugintester):
plugintester.hookcheck()
class TestSetupState: class TestSetupState:
def test_setup_prepare(self, testdir): def test_setup_prepare(self, testdir):

View File

@ -746,7 +746,3 @@ def test_repr_python_version(monkeypatch):
py.std.sys.version_info = x = (2,3) py.std.sys.version_info = x = (2,3)
assert repr_pythonversion() == str(x) assert repr_pythonversion() == str(x)
def test_generic(plugintester):
plugintester.hookcheck()
plugintester.hookcheck(cls=TerminalReporter)
plugintester.hookcheck(cls=CollectonlyReporter)

View File

@ -21,8 +21,6 @@ def pytest_funcarg__tmpdir(request):
# #
# =============================================================================== # ===============================================================================
# #
def test_generic(plugintester):
plugintester.hookcheck()
def test_funcarg(testdir): def test_funcarg(testdir):
from py.__.test.funcargs import FuncargRequest from py.__.test.funcargs import FuncargRequest

View File

@ -65,9 +65,6 @@ class UnitTestFunction(py.test.collect.Function):
instance.tearDown() instance.tearDown()
def test_generic(plugintester):
plugintester.hookcheck()
def test_simple_unittest(testdir): def test_simple_unittest(testdir):
testpath = testdir.makepyfile(""" testpath = testdir.makepyfile("""
import unittest import unittest

View File

@ -56,11 +56,8 @@ def pytest_terminal_summary(terminalreporter):
# #
# =============================================================================== # ===============================================================================
def test_generic(plugintester):
plugintester.hookcheck()
def test_xfail(plugintester, linecomp): def test_xfail(testdir, linecomp):
testdir = plugintester.testdir()
p = testdir.makepyfile(test_one=""" p = testdir.makepyfile(test_one="""
import py import py
pytest_plugins="pytest_xfail", pytest_plugins="pytest_xfail",

View File

@ -9,6 +9,8 @@ def check_old_use(mod, modname):
assert not hasattr(mod, clsname), (mod, clsname) assert not hasattr(mod, clsname), (mod, clsname)
class PluginManager(object): class PluginManager(object):
class Error(Exception):
"""signals a plugin specific error."""
def __init__(self, comregistry=None): def __init__(self, comregistry=None):
if comregistry is None: if comregistry is None:
comregistry = py._com.Registry() comregistry = py._com.Registry()
@ -34,6 +36,7 @@ class PluginManager(object):
assert name not in self.impname2plugin assert name not in self.impname2plugin
self.impname2plugin[name] = plugin self.impname2plugin[name] = plugin
self.hook.pytest_plugin_registered(plugin=plugin) self.hook.pytest_plugin_registered(plugin=plugin)
self._checkplugin(plugin)
self.comregistry.register(plugin) self.comregistry.register(plugin)
return True return True
@ -96,6 +99,46 @@ class PluginManager(object):
check_old_use(mod, modname) check_old_use(mod, modname)
self.register(mod) self.register(mod)
self.consider_module(mod) self.consider_module(mod)
def _checkplugin(self, plugin):
# =====================================================
# check plugin hooks
# =====================================================
methods = collectattr(plugin)
hooks = collectattr(api.PluginHooks)
stringio = py.std.StringIO.StringIO()
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 '__call__' in method_args:
method_args.remove('__call__')
hook = hooks[name]
hookargs = getargs(hook)
for arg, hookarg in zip(method_args, hookargs):
if arg != hookarg:
Print("argument mismatch: %r != %r" %(arg, hookarg))
Print("actual : %s" %(formatdef(method)))
Print("required:", formatdef(hook))
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
@ -169,3 +212,30 @@ def importplugin(importspec):
#print "syspath:", py.std.sys.path #print "syspath:", py.std.sys.path
#print "curdir:", py.std.os.getcwd() #print "curdir:", py.std.os.getcwd()
return __import__(importspec) # show the original exception return __import__(importspec) # show the original exception
def isgenerichook(name):
return name == "pytest_plugins" or \
name.startswith("pytest_funcarg__") or \
name.startswith("pytest_option_")
def getargs(func):
args = py.std.inspect.getargs(func.func_code)[0]
startindex = hasattr(func, 'im_self') 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.func_name,
py.std.inspect.formatargspec(*py.std.inspect.getargspec(func))
)

View File

@ -1,5 +1,5 @@
import py, os import py, os
from py.__.test.pluginmanager import PluginManager, canonical_importname from py.__.test.pluginmanager import PluginManager, canonical_importname, collectattr
class TestBootstrapping: class TestBootstrapping:
def test_consider_env_fails_to_import(self, monkeypatch): def test_consider_env_fails_to_import(self, monkeypatch):
@ -102,7 +102,8 @@ class TestBootstrapping:
def test_registry(self): def test_registry(self):
pp = PluginManager() pp = PluginManager()
a1, a2 = object(), object() class A: pass
a1, a2 = A(), A()
pp.register(a1) pp.register(a1)
assert pp.isregistered(a1) assert pp.isregistered(a1)
pp.register(a2) pp.register(a2)
@ -126,6 +127,20 @@ class TestBootstrapping:
#assert not pp.isregistered(mod2) #assert not pp.isregistered(mod2)
assert pp.getplugins() == [mod] # does not actually modify plugins assert pp.getplugins() == [mod] # does not actually modify plugins
def test_register_mismatch_method(self):
pp = PluginManager()
class hello:
def pytest_gurgel(self):
pass
py.test.raises(pp.Error, "pp.register(hello())")
def test_register_mismatch_arg(self):
pp = PluginManager()
class hello:
def pytest_configure(self, asd):
pass
excinfo = py.test.raises(pp.Error, "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':
impname = canonical_importname(name) impname = canonical_importname(name)
@ -223,3 +238,14 @@ class TestPytestPluginInteractions:
results = call.execute() results = call.execute()
assert results == [1,2,2] assert results == [1,2,2]
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']