diff --git a/contrib/pytest_coverage/__init__.py b/contrib/pytest_coverage/__init__.py index 748ae63fe..0feb34a17 100644 --- a/contrib/pytest_coverage/__init__.py +++ b/contrib/pytest_coverage/__init__.py @@ -327,22 +327,3 @@ class CoveragePlugin: 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 -''' diff --git a/contrib/pytest_twisted/__init__.py b/contrib/pytest_twisted/__init__.py index 29ce0c5b8..292fe97d9 100644 --- a/contrib/pytest_twisted/__init__.py +++ b/contrib/pytest_twisted/__init__.py @@ -115,10 +115,7 @@ gr_tests = greenlet.getcurrent() # plugin tests # =============================================================================== -def test_generic(plugintester): - plugintester.apicheck(TwistedPlugin) - - testdir = plugintester.testdir() +def test_generic(testdir): testdir.makepyfile(''' def test_pass(): pass diff --git a/py/test/plugin/conftest.py b/py/test/plugin/conftest.py index 5d5b81f4c..ebe8c85c6 100644 --- a/py/test/plugin/conftest.py +++ b/py/test/plugin/conftest.py @@ -1,6 +1,6 @@ import py -pytest_plugins = "pytester", "plugintester" +pytest_plugins = "pytester" def pytest_collect_file(path, parent): if path.basename.startswith("pytest_") and path.ext == ".py": diff --git a/py/test/plugin/pytest__pytest.py b/py/test/plugin/pytest__pytest.py index 5e720eb01..5934b5ca0 100644 --- a/py/test/plugin/pytest__pytest.py +++ b/py/test/plugin/pytest__pytest.py @@ -99,9 +99,6 @@ class HookRecorder: return l[0] -def test_generic(plugintester): - plugintester.hookcheck() - def test_hookrecorder_basic(): comregistry = py._com.Registry() rec = HookRecorder(comregistry) diff --git a/py/test/plugin/pytest_default.py b/py/test/plugin/pytest_default.py index d42a886ac..bc30dff49 100644 --- a/py/test/plugin/pytest_default.py +++ b/py/test/plugin/pytest_default.py @@ -172,9 +172,6 @@ def test_implied_different_sessions(tmpdir): assert x('-n3') == 'DSession' assert x('-f') == 'LooponfailingSession' -def test_generic(plugintester): - plugintester.hookcheck() - def test_plugin_specify(testdir): testdir.chdir() config = py.test.raises(ImportError, """ diff --git a/py/test/plugin/pytest_doctest.py b/py/test/plugin/pytest_doctest.py index ddd22f1fa..094247f57 100644 --- a/py/test/plugin/pytest_doctest.py +++ b/py/test/plugin/pytest_doctest.py @@ -156,8 +156,3 @@ class TestDoctests: " 1", "*test_txtfile_failing.txt:2: DocTestFailure" ]) - - -def test_generic(plugintester): - plugintester.hookcheck() - diff --git a/py/test/plugin/pytest_execnetcleanup.py b/py/test/plugin/pytest_execnetcleanup.py index 5c2b8883b..10a1a37a9 100644 --- a/py/test/plugin/pytest_execnetcleanup.py +++ b/py/test/plugin/pytest_execnetcleanup.py @@ -38,10 +38,6 @@ class Execnetcleanup: self._gateways[-1].exit() return res -def test_generic(plugintester): - plugintester.hookcheck(cls=Execnetcleanup) - plugintester.hookcheck() - @py.test.mark.xfail("clarify plugin registration/unregistration") def test_execnetplugin(testdir): p = ExecnetcleanupPlugin() diff --git a/py/test/plugin/pytest_figleaf.py b/py/test/plugin/pytest_figleaf.py index 220f86aa7..7fc71a194 100644 --- a/py/test/plugin/pytest_figleaf.py +++ b/py/test/plugin/pytest_figleaf.py @@ -50,9 +50,6 @@ def get_coverage(datafile, config): return coverage -def test_generic(plugintester): - plugintester.hookcheck() - def test_functional(testdir): py.test.importorskip("figleaf") testdir.plugins.append("figleaf") diff --git a/py/test/plugin/pytest_hooklog.py b/py/test/plugin/pytest_hooklog.py index 29fb5232f..6befb3c26 100644 --- a/py/test/plugin/pytest_hooklog.py +++ b/py/test/plugin/pytest_hooklog.py @@ -21,9 +21,6 @@ def pytest_unconfigure(config): # plugin tests # =============================================================================== -def test_generic(plugintester): - plugintester.hookcheck() - def test_functional(testdir): testdir.makepyfile(""" def test_pass(): diff --git a/py/test/plugin/pytest_iocapture.py b/py/test/plugin/pytest_iocapture.py index 0c0eb228f..dd111496c 100644 --- a/py/test/plugin/pytest_iocapture.py +++ b/py/test/plugin/pytest_iocapture.py @@ -35,9 +35,6 @@ class Capture: self._capture = self._captureclass() return res -def test_generic(plugintester): - plugintester.hookcheck() - class TestCapture: def test_std_functional(self, testdir): reprec = testdir.inline_runsource(""" diff --git a/py/test/plugin/pytest_plugintester.py b/py/test/plugin/pytest_plugintester.py deleted file mode 100644 index 60615fff3..000000000 --- a/py/test/plugin/pytest_plugintester.py +++ /dev/null @@ -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() diff --git a/py/test/plugin/pytest_pocoo.py b/py/test/plugin/pytest_pocoo.py index 9959b3179..07aa22964 100644 --- a/py/test/plugin/pytest_pocoo.py +++ b/py/test/plugin/pytest_pocoo.py @@ -38,9 +38,6 @@ def pytest_terminal_summary(terminalreporter): break -def test_apicheck(plugintester): - plugintester.hookcheck() - def test_toproxy(testdir, monkeypatch): l = [] class MockProxy: diff --git a/py/test/plugin/pytest_pylint.py b/py/test/plugin/pytest_pylint.py index 2bd46ad2d..429aee2c6 100644 --- a/py/test/plugin/pytest_pylint.py +++ b/py/test/plugin/pytest_pylint.py @@ -38,8 +38,4 @@ class PylintItem(py.test.collect.Item): print ">>>", print rating -def test_generic(plugintester): - plugintester.hookcheck(PylintPlugin) - -#def test_functional diff --git a/py/test/plugin/pytest_pytester.py b/py/test/plugin/pytest_pytester.py index b26b8e182..78d5cc8bc 100644 --- a/py/test/plugin/pytest_pytester.py +++ b/py/test/plugin/pytest_pytester.py @@ -26,9 +26,6 @@ def pytest_funcarg__reportrecorder(request): request.addfinalizer(lambda: reprec.comregistry.unregister(reprec)) return reprec -def test_generic(plugintester): - plugintester.hookcheck() - class RunResult: def __init__(self, ret, outlines, errlines): self.ret = ret diff --git a/py/test/plugin/pytest_restdoc.py b/py/test/plugin/pytest_restdoc.py index 8829dd5a7..f01d9c53f 100644 --- a/py/test/plugin/pytest_restdoc.py +++ b/py/test/plugin/pytest_restdoc.py @@ -348,8 +348,6 @@ def localrefcheck(tryfn, path, lineno): # # PLUGIN tests # -def test_generic(plugintester): - plugintester.hookcheck() def test_deindent(): assert deindent('foo') == 'foo' diff --git a/py/test/plugin/pytest_resultdb.py b/py/test/plugin/pytest_resultdb.py index 3cbfccceb..04247f88e 100644 --- a/py/test/plugin/pytest_resultdb.py +++ b/py/test/plugin/pytest_resultdb.py @@ -300,9 +300,8 @@ class TestWithFunctionIntegration: archive.init_db() return archive - def test_collection_report(self, plugintester): + def test_collection_report(self, testdir): py.test.skip("Needs a rewrite for db version.") - testdir = plugintester.testdir() ok = testdir.makepyfile(test_collection_ok="") skip = testdir.makepyfile(test_collection_skip="import py ; py.test.skip('hello')") fail = testdir.makepyfile(test_collection_fail="XXX") @@ -360,9 +359,7 @@ class TestWithFunctionIntegration: assert entry_lines[-1][0] == ' ' assert 'ValueError' in entry -def test_generic(plugintester): - plugintester.hookcheck() - testdir = plugintester.testdir() +def test_generic(testdir): testdir.makepyfile(""" import py def test_pass(): diff --git a/py/test/plugin/pytest_resultlog.py b/py/test/plugin/pytest_resultlog.py index 12c9f2a1e..f168c1cc3 100644 --- a/py/test/plugin/pytest_resultlog.py +++ b/py/test/plugin/pytest_resultlog.py @@ -163,8 +163,7 @@ class TestWithFunctionIntegration: testdir.runpytest(*args) return filter(None, resultlog.readlines(cr=0)) - def test_collection_report(self, plugintester): - testdir = plugintester.testdir() + def test_collection_report(self, testdir): ok = testdir.makepyfile(test_collection_ok="") skip = testdir.makepyfile(test_collection_skip="import py ; py.test.skip('hello')") fail = testdir.makepyfile(test_collection_fail="XXX") @@ -186,8 +185,7 @@ class TestWithFunctionIntegration: assert x.startswith(" ") assert "XXX" in "".join(lines[1:]) - def test_log_test_outcomes(self, plugintester): - testdir = plugintester.testdir() + def test_log_test_outcomes(self, testdir): mod = testdir.makepyfile(test_mod=""" import py def test_pass(): pass @@ -224,9 +222,7 @@ class TestWithFunctionIntegration: assert entry_lines[-1][0] == ' ' assert 'ValueError' in entry -def test_generic(plugintester, LineMatcher): - plugintester.hookcheck() - testdir = plugintester.testdir() +def test_generic(testdir, LineMatcher): testdir.plugins.append("resultlog") testdir.makepyfile(""" import py diff --git a/py/test/plugin/pytest_runner.py b/py/test/plugin/pytest_runner.py index 1fb196d5e..430f43eeb 100644 --- a/py/test/plugin/pytest_runner.py +++ b/py/test/plugin/pytest_runner.py @@ -126,8 +126,6 @@ class ItemFixtureReport(BaseReport): # # =============================================================================== -def test_generic(plugintester): - plugintester.hookcheck() class TestSetupState: def test_setup_prepare(self, testdir): diff --git a/py/test/plugin/pytest_terminal.py b/py/test/plugin/pytest_terminal.py index 0170e48dd..bedf91b0b 100644 --- a/py/test/plugin/pytest_terminal.py +++ b/py/test/plugin/pytest_terminal.py @@ -746,7 +746,3 @@ def test_repr_python_version(monkeypatch): py.std.sys.version_info = x = (2,3) assert repr_pythonversion() == str(x) -def test_generic(plugintester): - plugintester.hookcheck() - plugintester.hookcheck(cls=TerminalReporter) - plugintester.hookcheck(cls=CollectonlyReporter) diff --git a/py/test/plugin/pytest_tmpdir.py b/py/test/plugin/pytest_tmpdir.py index bc6cbc61e..523c5cd51 100644 --- a/py/test/plugin/pytest_tmpdir.py +++ b/py/test/plugin/pytest_tmpdir.py @@ -21,8 +21,6 @@ def pytest_funcarg__tmpdir(request): # # =============================================================================== # -def test_generic(plugintester): - plugintester.hookcheck() def test_funcarg(testdir): from py.__.test.funcargs import FuncargRequest diff --git a/py/test/plugin/pytest_unittest.py b/py/test/plugin/pytest_unittest.py index 8c0f0c8a4..ad024ecdc 100644 --- a/py/test/plugin/pytest_unittest.py +++ b/py/test/plugin/pytest_unittest.py @@ -65,9 +65,6 @@ class UnitTestFunction(py.test.collect.Function): instance.tearDown() -def test_generic(plugintester): - plugintester.hookcheck() - def test_simple_unittest(testdir): testpath = testdir.makepyfile(""" import unittest diff --git a/py/test/plugin/pytest_xfail.py b/py/test/plugin/pytest_xfail.py index 8bbd29c5b..4a95b940f 100644 --- a/py/test/plugin/pytest_xfail.py +++ b/py/test/plugin/pytest_xfail.py @@ -56,11 +56,8 @@ def pytest_terminal_summary(terminalreporter): # # =============================================================================== -def test_generic(plugintester): - plugintester.hookcheck() -def test_xfail(plugintester, linecomp): - testdir = plugintester.testdir() +def test_xfail(testdir, linecomp): p = testdir.makepyfile(test_one=""" import py pytest_plugins="pytest_xfail", diff --git a/py/test/pluginmanager.py b/py/test/pluginmanager.py index 058cd6e1d..e7f384a79 100644 --- a/py/test/pluginmanager.py +++ b/py/test/pluginmanager.py @@ -9,6 +9,8 @@ def check_old_use(mod, modname): assert not hasattr(mod, clsname), (mod, clsname) class PluginManager(object): + class Error(Exception): + """signals a plugin specific error.""" def __init__(self, comregistry=None): if comregistry is None: comregistry = py._com.Registry() @@ -34,6 +36,7 @@ class PluginManager(object): assert name not in self.impname2plugin self.impname2plugin[name] = plugin self.hook.pytest_plugin_registered(plugin=plugin) + self._checkplugin(plugin) self.comregistry.register(plugin) return True @@ -96,6 +99,46 @@ class PluginManager(object): check_old_use(mod, modname) self.register(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 @@ -169,3 +212,30 @@ def importplugin(importspec): #print "syspath:", py.std.sys.path #print "curdir:", py.std.os.getcwd() 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)) + ) + diff --git a/py/test/testing/test_pluginmanager.py b/py/test/testing/test_pluginmanager.py index 6a254035a..af96b9fb8 100644 --- a/py/test/testing/test_pluginmanager.py +++ b/py/test/testing/test_pluginmanager.py @@ -1,5 +1,5 @@ import py, os -from py.__.test.pluginmanager import PluginManager, canonical_importname +from py.__.test.pluginmanager import PluginManager, canonical_importname, collectattr class TestBootstrapping: def test_consider_env_fails_to_import(self, monkeypatch): @@ -102,7 +102,8 @@ class TestBootstrapping: def test_registry(self): pp = PluginManager() - a1, a2 = object(), object() + class A: pass + a1, a2 = A(), A() pp.register(a1) assert pp.isregistered(a1) pp.register(a2) @@ -126,6 +127,20 @@ class TestBootstrapping: #assert not pp.isregistered(mod2) 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): for name in 'xyz', 'pytest_xyz', 'pytest_Xyz', 'Xyz': impname = canonical_importname(name) @@ -223,3 +238,14 @@ class TestPytestPluginInteractions: results = call.execute() 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']