summaryrefslogtreecommitdiffstats
path: root/third_party/python/pluggy/testing
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/python/pluggy/testing')
-rw-r--r--third_party/python/pluggy/testing/benchmark.py59
-rw-r--r--third_party/python/pluggy/testing/conftest.py30
-rw-r--r--third_party/python/pluggy/testing/test_details.py103
-rw-r--r--third_party/python/pluggy/testing/test_helpers.py68
-rw-r--r--third_party/python/pluggy/testing/test_hookrelay.py210
-rw-r--r--third_party/python/pluggy/testing/test_method_ordering.py322
-rw-r--r--third_party/python/pluggy/testing/test_multicall.py194
-rw-r--r--third_party/python/pluggy/testing/test_pluginmanager.py374
-rw-r--r--third_party/python/pluggy/testing/test_tracer.py89
9 files changed, 1449 insertions, 0 deletions
diff --git a/third_party/python/pluggy/testing/benchmark.py b/third_party/python/pluggy/testing/benchmark.py
new file mode 100644
index 0000000000..5a913e9d41
--- /dev/null
+++ b/third_party/python/pluggy/testing/benchmark.py
@@ -0,0 +1,59 @@
+"""
+Benchmarking and performance tests.
+"""
+import pytest
+from pluggy import (_multicall, _legacymulticall, HookImpl, HookspecMarker,
+ HookimplMarker)
+
+hookspec = HookspecMarker("example")
+hookimpl = HookimplMarker("example")
+
+
+def MC(methods, kwargs, callertype, firstresult=False):
+ hookfuncs = []
+ for method in methods:
+ f = HookImpl(None, "<temp>", method, method.example_impl)
+ hookfuncs.append(f)
+ return callertype(hookfuncs, kwargs, {"firstresult": firstresult})
+
+
+@hookimpl
+def hook(arg1, arg2, arg3):
+ return arg1, arg2, arg3
+
+
+@hookimpl(hookwrapper=True)
+def wrapper(arg1, arg2, arg3):
+ yield
+
+
+@pytest.fixture(
+ params=[10, 100],
+ ids="hooks={}".format,
+)
+def hooks(request):
+ return [hook for i in range(request.param)]
+
+
+@pytest.fixture(
+ params=[10, 100],
+ ids="wrappers={}".format,
+)
+def wrappers(request):
+ return [wrapper for i in range(request.param)]
+
+
+@pytest.fixture(
+ params=[_multicall, _legacymulticall],
+ ids=lambda item: item.__name__
+)
+def callertype(request):
+ return request.param
+
+
+def inner_exec(methods, callertype):
+ return MC(methods, {'arg1': 1, 'arg2': 2, 'arg3': 3}, callertype)
+
+
+def test_hook_and_wrappers_speed(benchmark, hooks, wrappers, callertype):
+ benchmark(inner_exec, hooks + wrappers, callertype)
diff --git a/third_party/python/pluggy/testing/conftest.py b/third_party/python/pluggy/testing/conftest.py
new file mode 100644
index 0000000000..3d61a349c8
--- /dev/null
+++ b/third_party/python/pluggy/testing/conftest.py
@@ -0,0 +1,30 @@
+import pytest
+
+
+@pytest.fixture(
+ params=[
+ lambda spec: spec,
+ lambda spec: spec()
+ ],
+ ids=[
+ "spec-is-class",
+ "spec-is-instance"
+ ],
+)
+def he_pm(request, pm):
+ from pluggy import HookspecMarker
+ hookspec = HookspecMarker("example")
+
+ class Hooks(object):
+ @hookspec
+ def he_method1(self, arg):
+ return arg + 1
+
+ pm.add_hookspecs(request.param(Hooks))
+ return pm
+
+
+@pytest.fixture
+def pm():
+ from pluggy import PluginManager
+ return PluginManager("example")
diff --git a/third_party/python/pluggy/testing/test_details.py b/third_party/python/pluggy/testing/test_details.py
new file mode 100644
index 0000000000..2fad198d95
--- /dev/null
+++ b/third_party/python/pluggy/testing/test_details.py
@@ -0,0 +1,103 @@
+import warnings
+
+import pytest
+
+from pluggy import PluginManager, HookimplMarker, HookspecMarker, _Result
+
+hookspec = HookspecMarker("example")
+hookimpl = HookimplMarker("example")
+
+
+def test_parse_hookimpl_override():
+ class MyPluginManager(PluginManager):
+ def parse_hookimpl_opts(self, module_or_class, name):
+ opts = PluginManager.parse_hookimpl_opts(
+ self, module_or_class, name)
+ if opts is None:
+ if name.startswith("x1"):
+ opts = {}
+ return opts
+
+ class Plugin(object):
+ def x1meth(self):
+ pass
+
+ @hookimpl(hookwrapper=True, tryfirst=True)
+ def x1meth2(self):
+ pass
+
+ class Spec(object):
+ @hookspec
+ def x1meth(self):
+ pass
+
+ @hookspec
+ def x1meth2(self):
+ pass
+
+ pm = MyPluginManager(hookspec.project_name)
+ pm.register(Plugin())
+ pm.add_hookspecs(Spec)
+ assert not pm.hook.x1meth._nonwrappers[0].hookwrapper
+ assert not pm.hook.x1meth._nonwrappers[0].tryfirst
+ assert not pm.hook.x1meth._nonwrappers[0].trylast
+ assert not pm.hook.x1meth._nonwrappers[0].optionalhook
+
+ assert pm.hook.x1meth2._wrappers[0].tryfirst
+ assert pm.hook.x1meth2._wrappers[0].hookwrapper
+
+
+def test_plugin_getattr_raises_errors():
+ """Pluggy must be able to handle plugins which raise weird exceptions
+ when getattr() gets called (#11).
+ """
+ class DontTouchMe(object):
+ def __getattr__(self, x):
+ raise Exception('cant touch me')
+
+ class Module(object):
+ pass
+
+ module = Module()
+ module.x = DontTouchMe()
+
+ pm = PluginManager(hookspec.project_name)
+ # register() would raise an error
+ pm.register(module, 'donttouch')
+ assert pm.get_plugin('donttouch') is module
+
+
+def test_warning_on_call_vs_hookspec_arg_mismatch():
+ """Verify that is a hook is called with less arguments then defined in the
+ spec that a warning is emitted.
+ """
+ class Spec:
+ @hookspec
+ def myhook(self, arg1, arg2):
+ pass
+
+ class Plugin:
+ @hookimpl
+ def myhook(self, arg1):
+ pass
+
+ pm = PluginManager(hookspec.project_name)
+ pm.register(Plugin())
+ pm.add_hookspecs(Spec())
+
+ with warnings.catch_warnings(record=True) as warns:
+ warnings.simplefilter('always')
+
+ # calling should trigger a warning
+ pm.hook.myhook(arg1=1)
+
+ assert len(warns) == 1
+ warning = warns[-1]
+ assert issubclass(warning.category, Warning)
+ assert "Argument(s) ('arg2',)" in str(warning.message)
+
+
+def test_result_deprecated():
+ r = _Result(10, None)
+ with pytest.deprecated_call():
+ assert r.result == 10
diff --git a/third_party/python/pluggy/testing/test_helpers.py b/third_party/python/pluggy/testing/test_helpers.py
new file mode 100644
index 0000000000..b178096844
--- /dev/null
+++ b/third_party/python/pluggy/testing/test_helpers.py
@@ -0,0 +1,68 @@
+from pluggy import _formatdef, varnames
+
+
+def test_varnames():
+ def f(x):
+ i = 3 # noqa
+
+ class A(object):
+ def f(self, y):
+ pass
+
+ class B(object):
+ def __call__(self, z):
+ pass
+
+ assert varnames(f) == (("x",), ())
+ assert varnames(A().f) == (('y',), ())
+ assert varnames(B()) == (('z',), ())
+
+
+def test_varnames_default():
+ def f(x, y=3):
+ pass
+
+ assert varnames(f) == (("x",), ("y",))
+
+
+def test_varnames_class():
+ class C(object):
+ def __init__(self, x):
+ pass
+
+ class D(object):
+ pass
+
+ class E(object):
+ def __init__(self, x):
+ pass
+
+ class F(object):
+ pass
+
+ assert varnames(C) == (("x",), ())
+ assert varnames(D) == ((), ())
+ assert varnames(E) == (("x",), ())
+ assert varnames(F) == ((), ())
+
+
+def test_formatdef():
+ def function1():
+ pass
+
+ assert _formatdef(function1) == 'function1()'
+
+ def function2(arg1):
+ pass
+
+ assert _formatdef(function2) == "function2(arg1)"
+
+ def function3(arg1, arg2="qwe"):
+ pass
+
+ assert _formatdef(function3) == "function3(arg1, arg2='qwe')"
+
+ def function4(arg1, *args, **kwargs):
+ pass
+
+ assert _formatdef(function4) == "function4(arg1, *args, **kwargs)"
diff --git a/third_party/python/pluggy/testing/test_hookrelay.py b/third_party/python/pluggy/testing/test_hookrelay.py
new file mode 100644
index 0000000000..5e7821bed8
--- /dev/null
+++ b/third_party/python/pluggy/testing/test_hookrelay.py
@@ -0,0 +1,210 @@
+import pytest
+from pluggy import PluginValidationError, HookimplMarker, HookspecMarker
+
+
+hookspec = HookspecMarker("example")
+hookimpl = HookimplMarker("example")
+
+
+def test_happypath(pm):
+ class Api(object):
+ @hookspec
+ def hello(self, arg):
+ "api hook 1"
+
+ pm.add_hookspecs(Api)
+ hook = pm.hook
+ assert hasattr(hook, 'hello')
+ assert repr(hook.hello).find("hello") != -1
+
+ class Plugin(object):
+ @hookimpl
+ def hello(self, arg):
+ return arg + 1
+
+ plugin = Plugin()
+ pm.register(plugin)
+ out = hook.hello(arg=3)
+ assert out == [4]
+ assert not hasattr(hook, 'world')
+ pm.unregister(plugin)
+ assert hook.hello(arg=3) == []
+
+
+def test_argmismatch(pm):
+ class Api(object):
+ @hookspec
+ def hello(self, arg):
+ "api hook 1"
+
+ pm.add_hookspecs(Api)
+
+ class Plugin(object):
+ @hookimpl
+ def hello(self, argwrong):
+ pass
+
+ with pytest.raises(PluginValidationError) as exc:
+ pm.register(Plugin())
+
+ assert "argwrong" in str(exc.value)
+
+
+def test_only_kwargs(pm):
+ class Api(object):
+ @hookspec
+ def hello(self, arg):
+ "api hook 1"
+
+ pm.add_hookspecs(Api)
+ with pytest.raises(TypeError) as exc:
+ pm.hook.hello(3)
+
+ comprehensible = "hook calling supports only keyword arguments"
+ assert comprehensible in str(exc.value)
+
+
+def test_call_order(pm):
+ class Api(object):
+ @hookspec
+ def hello(self, arg):
+ "api hook 1"
+
+ pm.add_hookspecs(Api)
+
+ class Plugin1(object):
+ @hookimpl
+ def hello(self, arg):
+ return 1
+
+ class Plugin2(object):
+ @hookimpl
+ def hello(self, arg):
+ return 2
+
+ class Plugin3(object):
+ @hookimpl
+ def hello(self, arg):
+ return 3
+
+ class Plugin4(object):
+ @hookimpl(hookwrapper=True)
+ def hello(self, arg):
+ assert arg == 0
+ outcome = yield
+ assert outcome.get_result() == [3, 2, 1]
+
+ pm.register(Plugin1())
+ pm.register(Plugin2())
+ pm.register(Plugin3())
+ pm.register(Plugin4()) # hookwrapper should get same list result
+ res = pm.hook.hello(arg=0)
+ assert res == [3, 2, 1]
+
+
+def test_firstresult_definition(pm):
+ class Api(object):
+ @hookspec(firstresult=True)
+ def hello(self, arg):
+ "api hook 1"
+
+ pm.add_hookspecs(Api)
+
+ class Plugin1(object):
+ @hookimpl
+ def hello(self, arg):
+ return arg + 1
+
+ class Plugin2(object):
+ @hookimpl
+ def hello(self, arg):
+ return arg - 1
+
+ class Plugin3(object):
+ @hookimpl
+ def hello(self, arg):
+ return None
+
+ class Plugin4(object):
+ @hookimpl(hookwrapper=True)
+ def hello(self, arg):
+ assert arg == 3
+ outcome = yield
+ assert outcome.get_result() == 2
+
+ pm.register(Plugin1()) # discarded - not the last registered plugin
+ pm.register(Plugin2()) # used as result
+ pm.register(Plugin3()) # None result is ignored
+ pm.register(Plugin4()) # hookwrapper should get same non-list result
+ res = pm.hook.hello(arg=3)
+ assert res == 2
+
+
+def test_firstresult_force_result(pm):
+ """Verify forcing a result in a wrapper.
+ """
+ class Api(object):
+ @hookspec(firstresult=True)
+ def hello(self, arg):
+ "api hook 1"
+
+ pm.add_hookspecs(Api)
+
+ class Plugin1(object):
+ @hookimpl
+ def hello(self, arg):
+ return arg + 1
+
+ class Plugin2(object):
+ @hookimpl(hookwrapper=True)
+ def hello(self, arg):
+ assert arg == 3
+ outcome = yield
+ assert outcome.get_result() == 4
+ outcome.force_result(0)
+
+ class Plugin3(object):
+ @hookimpl
+ def hello(self, arg):
+ return None
+
+ pm.register(Plugin1())
+ pm.register(Plugin2()) # wrapper
+ pm.register(Plugin3()) # ignored since returns None
+ res = pm.hook.hello(arg=3)
+ assert res == 0 # this result is forced and not a list
+
+
+def test_firstresult_returns_none(pm):
+ """If None results are returned by underlying implementations ensure
+ the multi-call loop returns a None value.
+ """
+ class Api(object):
+ @hookspec(firstresult=True)
+ def hello(self, arg):
+ "api hook 1"
+
+ pm.add_hookspecs(Api)
+
+ class Plugin1(object):
+ @hookimpl
+ def hello(self, arg):
+ return None
+
+ pm.register(Plugin1())
+ res = pm.hook.hello(arg=3)
+ assert res is None
+
+
+def test_firstresult_no_plugin(pm):
+ """If no implementations/plugins have been registered for a firstresult
+ hook the multi-call loop should return a None value.
+ """
+ class Api(object):
+ @hookspec(firstresult=True)
+ def hello(self, arg):
+ "api hook 1"
+
+ pm.add_hookspecs(Api)
+ res = pm.hook.hello(arg=3)
+ assert res is None
diff --git a/third_party/python/pluggy/testing/test_method_ordering.py b/third_party/python/pluggy/testing/test_method_ordering.py
new file mode 100644
index 0000000000..9584a0ae5a
--- /dev/null
+++ b/third_party/python/pluggy/testing/test_method_ordering.py
@@ -0,0 +1,322 @@
+import pytest
+
+
+import sys
+import types
+
+from pluggy import PluginManager, HookImpl, HookimplMarker, HookspecMarker
+
+hookspec = HookspecMarker("example")
+hookimpl = HookimplMarker("example")
+
+
+@pytest.fixture
+def hc(pm):
+ class Hooks(object):
+ @hookspec
+ def he_method1(self, arg):
+ pass
+ pm.add_hookspecs(Hooks)
+ return pm.hook.he_method1
+
+
+@pytest.fixture
+def addmeth(hc):
+ def addmeth(tryfirst=False, trylast=False, hookwrapper=False):
+ def wrap(func):
+ hookimpl(tryfirst=tryfirst, trylast=trylast,
+ hookwrapper=hookwrapper)(func)
+ hc._add_hookimpl(HookImpl(None, "<temp>", func, func.example_impl))
+ return func
+ return wrap
+ return addmeth
+
+
+def funcs(hookmethods):
+ return [hookmethod.function for hookmethod in hookmethods]
+
+
+def test_adding_nonwrappers(hc, addmeth):
+ @addmeth()
+ def he_method1():
+ pass
+
+ @addmeth()
+ def he_method2():
+ pass
+
+ @addmeth()
+ def he_method3():
+ pass
+ assert funcs(hc._nonwrappers) == [he_method1, he_method2, he_method3]
+
+
+def test_adding_nonwrappers_trylast(hc, addmeth):
+ @addmeth()
+ def he_method1_middle():
+ pass
+
+ @addmeth(trylast=True)
+ def he_method1():
+ pass
+
+ @addmeth()
+ def he_method1_b():
+ pass
+ assert funcs(hc._nonwrappers) == [he_method1, he_method1_middle, he_method1_b]
+
+
+def test_adding_nonwrappers_trylast3(hc, addmeth):
+ @addmeth()
+ def he_method1_a():
+ pass
+
+ @addmeth(trylast=True)
+ def he_method1_b():
+ pass
+
+ @addmeth()
+ def he_method1_c():
+ pass
+
+ @addmeth(trylast=True)
+ def he_method1_d():
+ pass
+ assert funcs(hc._nonwrappers) == \
+ [he_method1_d, he_method1_b, he_method1_a, he_method1_c]
+
+
+def test_adding_nonwrappers_trylast2(hc, addmeth):
+ @addmeth()
+ def he_method1_middle():
+ pass
+
+ @addmeth()
+ def he_method1_b():
+ pass
+
+ @addmeth(trylast=True)
+ def he_method1():
+ pass
+ assert funcs(hc._nonwrappers) == \
+ [he_method1, he_method1_middle, he_method1_b]
+
+
+def test_adding_nonwrappers_tryfirst(hc, addmeth):
+ @addmeth(tryfirst=True)
+ def he_method1():
+ pass
+
+ @addmeth()
+ def he_method1_middle():
+ pass
+
+ @addmeth()
+ def he_method1_b():
+ pass
+ assert funcs(hc._nonwrappers) == [
+ he_method1_middle, he_method1_b, he_method1]
+
+
+def test_adding_wrappers_ordering(hc, addmeth):
+ @addmeth(hookwrapper=True)
+ def he_method1():
+ pass
+
+ @addmeth()
+ def he_method1_middle():
+ pass
+
+ @addmeth(hookwrapper=True)
+ def he_method3():
+ pass
+
+ assert funcs(hc._nonwrappers) == [he_method1_middle]
+ assert funcs(hc._wrappers) == [he_method1, he_method3]
+
+
+def test_adding_wrappers_ordering_tryfirst(hc, addmeth):
+ @addmeth(hookwrapper=True, tryfirst=True)
+ def he_method1():
+ pass
+
+ @addmeth(hookwrapper=True)
+ def he_method2():
+ pass
+
+ assert hc._nonwrappers == []
+ assert funcs(hc._wrappers) == [he_method2, he_method1]
+
+
+def test_hookspec(pm):
+ class HookSpec(object):
+ @hookspec()
+ def he_myhook1(arg1):
+ pass
+
+ @hookspec(firstresult=True)
+ def he_myhook2(arg1):
+ pass
+
+ @hookspec(firstresult=False)
+ def he_myhook3(arg1):
+ pass
+
+ pm.add_hookspecs(HookSpec)
+ assert not pm.hook.he_myhook1.spec_opts["firstresult"]
+ assert pm.hook.he_myhook2.spec_opts["firstresult"]
+ assert not pm.hook.he_myhook3.spec_opts["firstresult"]
+
+
+@pytest.mark.parametrize('name', ["hookwrapper", "optionalhook", "tryfirst", "trylast"])
+@pytest.mark.parametrize('val', [True, False])
+def test_hookimpl(name, val):
+ @hookimpl(**{name: val})
+ def he_myhook1(arg1):
+ pass
+ if val:
+ assert he_myhook1.example_impl.get(name)
+ else:
+ assert not hasattr(he_myhook1, name)
+
+
+def test_load_setuptools_instantiation(monkeypatch, pm):
+ pkg_resources = pytest.importorskip("pkg_resources")
+
+ def my_iter(name):
+ assert name == "hello"
+
+ class EntryPoint(object):
+ name = "myname"
+ dist = None
+
+ def load(self):
+ class PseudoPlugin(object):
+ x = 42
+ return PseudoPlugin()
+
+ return iter([EntryPoint()])
+
+ monkeypatch.setattr(pkg_resources, 'iter_entry_points', my_iter)
+ num = pm.load_setuptools_entrypoints("hello")
+ assert num == 1
+ plugin = pm.get_plugin("myname")
+ assert plugin.x == 42
+ assert pm.list_plugin_distinfo() == [(plugin, None)]
+
+
+def test_load_setuptools_not_installed(monkeypatch, pm):
+ monkeypatch.setitem(
+ sys.modules, 'pkg_resources',
+ types.ModuleType("pkg_resources"))
+
+ with pytest.raises(ImportError):
+ pm.load_setuptools_entrypoints("qwe")
+
+
+def test_add_tracefuncs(he_pm):
+ out = []
+
+ class api1(object):
+ @hookimpl
+ def he_method1(self):
+ out.append("he_method1-api1")
+
+ class api2(object):
+ @hookimpl
+ def he_method1(self):
+ out.append("he_method1-api2")
+
+ he_pm.register(api1())
+ he_pm.register(api2())
+
+ def before(hook_name, hook_impls, kwargs):
+ out.append((hook_name, list(hook_impls), kwargs))
+
+ def after(outcome, hook_name, hook_impls, kwargs):
+ out.append((outcome, hook_name, list(hook_impls), kwargs))
+
+ undo = he_pm.add_hookcall_monitoring(before, after)
+
+ he_pm.hook.he_method1(arg=1)
+ assert len(out) == 4
+ assert out[0][0] == "he_method1"
+ assert len(out[0][1]) == 2
+ assert isinstance(out[0][2], dict)
+ assert out[1] == "he_method1-api2"
+ assert out[2] == "he_method1-api1"
+ assert len(out[3]) == 4
+ assert out[3][1] == out[0][0]
+
+ undo()
+ he_pm.hook.he_method1(arg=1)
+ assert len(out) == 4 + 2
+
+
+def test_hook_tracing(he_pm):
+ saveindent = []
+
+ class api1(object):
+ @hookimpl
+ def he_method1(self):
+ saveindent.append(he_pm.trace.root.indent)
+
+ class api2(object):
+ @hookimpl
+ def he_method1(self):
+ saveindent.append(he_pm.trace.root.indent)
+ raise ValueError()
+
+ he_pm.register(api1())
+ out = []
+ he_pm.trace.root.setwriter(out.append)
+ undo = he_pm.enable_tracing()
+ try:
+ indent = he_pm.trace.root.indent
+ he_pm.hook.he_method1(arg=1)
+ assert indent == he_pm.trace.root.indent
+ assert len(out) == 2
+ assert 'he_method1' in out[0]
+ assert 'finish' in out[1]
+
+ out[:] = []
+ he_pm.register(api2())
+
+ with pytest.raises(ValueError):
+ he_pm.hook.he_method1(arg=1)
+ assert he_pm.trace.root.indent == indent
+ assert saveindent[0] > indent
+ finally:
+ undo()
+
+
+@pytest.mark.parametrize('include_hookspec', [True, False])
+def test_prefix_hookimpl(include_hookspec):
+ pm = PluginManager(hookspec.project_name, "hello_")
+
+ if include_hookspec:
+ class HookSpec(object):
+ @hookspec
+ def hello_myhook(self, arg1):
+ """ add to arg1 """
+
+ pm.add_hookspecs(HookSpec)
+
+ class Plugin(object):
+ def hello_myhook(self, arg1):
+ return arg1 + 1
+
+ pm.register(Plugin())
+ pm.register(Plugin())
+ results = pm.hook.hello_myhook(arg1=17)
+ assert results == [18, 18]
+
+
+def test_prefix_hookimpl_dontmatch_module():
+ pm = PluginManager(hookspec.project_name, "hello_")
+
+ class BadPlugin(object):
+ hello_module = __import__('email')
+
+ pm.register(BadPlugin())
+ pm.check_pending()
diff --git a/third_party/python/pluggy/testing/test_multicall.py b/third_party/python/pluggy/testing/test_multicall.py
new file mode 100644
index 0000000000..860a209b66
--- /dev/null
+++ b/third_party/python/pluggy/testing/test_multicall.py
@@ -0,0 +1,194 @@
+import pytest
+
+from pluggy import _multicall, _legacymulticall, HookImpl, HookCallError
+from pluggy.callers import _LegacyMultiCall
+from pluggy import HookspecMarker, HookimplMarker
+
+
+hookspec = HookspecMarker("example")
+hookimpl = HookimplMarker("example")
+
+
+def test_uses_copy_of_methods():
+ out = [lambda: 42]
+ mc = _LegacyMultiCall(out, {})
+ repr(mc)
+ out[:] = []
+ res = mc.execute()
+ return res == 42
+
+
+def MC(methods, kwargs, firstresult=False):
+ caller = _multicall
+ hookfuncs = []
+ for method in methods:
+ f = HookImpl(None, "<temp>", method, method.example_impl)
+ hookfuncs.append(f)
+ if '__multicall__' in f.argnames:
+ caller = _legacymulticall
+ return caller(hookfuncs, kwargs, firstresult=firstresult)
+
+
+def test_call_passing():
+ class P1(object):
+ @hookimpl
+ def m(self, __multicall__, x):
+ assert len(__multicall__.results) == 1
+ assert not __multicall__.hook_impls
+ return 17
+
+ class P2(object):
+ @hookimpl
+ def m(self, __multicall__, x):
+ assert __multicall__.results == []
+ assert __multicall__.hook_impls
+ return 23
+
+ p1 = P1()
+ p2 = P2()
+ reslist = MC([p1.m, p2.m], {"x": 23})
+ assert len(reslist) == 2
+ # ensure reversed order
+ assert reslist == [23, 17]
+
+
+def test_keyword_args():
+ @hookimpl
+ def f(x):
+ return x + 1
+
+ class A(object):
+ @hookimpl
+ def f(self, x, y):
+ return x + y
+
+ reslist = MC([f, A().f], dict(x=23, y=24))
+ assert reslist == [24 + 23, 24]
+
+
+def test_keyword_args_with_defaultargs():
+ @hookimpl
+ def f(x, z=1):
+ return x + z
+ reslist = MC([f], dict(x=23, y=24))
+ assert reslist == [24]
+
+
+def test_tags_call_error():
+ @hookimpl
+ def f(x):
+ return x
+ with pytest.raises(HookCallError):
+ MC([f], {})
+
+
+def test_call_subexecute():
+ @hookimpl
+ def m(__multicall__):
+ subresult = __multicall__.execute()
+ return subresult + 1
+
+ @hookimpl
+ def n():
+ return 1
+
+ res = MC([n, m], {}, firstresult=True)
+ assert res == 2
+
+
+def test_call_none_is_no_result():
+ @hookimpl
+ def m1():
+ return 1
+
+ @hookimpl
+ def m2():
+ return None
+
+ res = MC([m1, m2], {}, firstresult=True)
+ assert res == 1
+ res = MC([m1, m2], {}, {})
+ assert res == [1]
+
+
+def test_hookwrapper():
+ out = []
+
+ @hookimpl(hookwrapper=True)
+ def m1():
+ out.append("m1 init")
+ yield None
+ out.append("m1 finish")
+
+ @hookimpl
+ def m2():
+ out.append("m2")
+ return 2
+
+ res = MC([m2, m1], {})
+ assert res == [2]
+ assert out == ["m1 init", "m2", "m1 finish"]
+ out[:] = []
+ res = MC([m2, m1], {}, firstresult=True)
+ assert res == 2
+ assert out == ["m1 init", "m2", "m1 finish"]
+
+
+def test_hookwrapper_order():
+ out = []
+
+ @hookimpl(hookwrapper=True)
+ def m1():
+ out.append("m1 init")
+ yield 1
+ out.append("m1 finish")
+
+ @hookimpl(hookwrapper=True)
+ def m2():
+ out.append("m2 init")
+ yield 2
+ out.append("m2 finish")
+
+ res = MC([m2, m1], {})
+ assert res == []
+ assert out == ["m1 init", "m2 init", "m2 finish", "m1 finish"]
+
+
+def test_hookwrapper_not_yield():
+ @hookimpl(hookwrapper=True)
+ def m1():
+ pass
+
+ with pytest.raises(TypeError):
+ MC([m1], {})
+
+
+def test_hookwrapper_too_many_yield():
+ @hookimpl(hookwrapper=True)
+ def m1():
+ yield 1
+ yield 2
+
+ with pytest.raises(RuntimeError) as ex:
+ MC([m1], {})
+ assert "m1" in str(ex.value)
+ assert (__file__ + ':') in str(ex.value)
+
+
+@pytest.mark.parametrize("exc", [ValueError, SystemExit])
+def test_hookwrapper_exception(exc):
+ out = []
+
+ @hookimpl(hookwrapper=True)
+ def m1():
+ out.append("m1 init")
+ yield None
+ out.append("m1 finish")
+
+ @hookimpl
+ def m2():
+ raise exc
+
+ with pytest.raises(exc):
+ MC([m2, m1], {})
+ assert out == ["m1 init", "m1 finish"]
diff --git a/third_party/python/pluggy/testing/test_pluginmanager.py b/third_party/python/pluggy/testing/test_pluginmanager.py
new file mode 100644
index 0000000000..e2c86cc644
--- /dev/null
+++ b/third_party/python/pluggy/testing/test_pluginmanager.py
@@ -0,0 +1,374 @@
+import pytest
+import types
+
+from pluggy import (PluginValidationError,
+ HookCallError, HookimplMarker, HookspecMarker)
+
+
+hookspec = HookspecMarker("example")
+hookimpl = HookimplMarker("example")
+
+
+def test_plugin_double_register(pm):
+ pm.register(42, name="abc")
+ with pytest.raises(ValueError):
+ pm.register(42, name="abc")
+ with pytest.raises(ValueError):
+ pm.register(42, name="def")
+
+
+def test_pm(pm):
+ class A(object):
+ pass
+
+ a1, a2 = A(), A()
+ pm.register(a1)
+ assert pm.is_registered(a1)
+ pm.register(a2, "hello")
+ assert pm.is_registered(a2)
+ out = pm.get_plugins()
+ assert a1 in out
+ assert a2 in out
+ assert pm.get_plugin('hello') == a2
+ assert pm.unregister(a1) == a1
+ assert not pm.is_registered(a1)
+
+ out = pm.list_name_plugin()
+ assert len(out) == 1
+ assert out == [("hello", a2)]
+
+
+def test_has_plugin(pm):
+ class A(object):
+ pass
+
+ a1 = A()
+ pm.register(a1, 'hello')
+ assert pm.is_registered(a1)
+ assert pm.has_plugin('hello')
+
+
+def test_register_dynamic_attr(he_pm):
+ class A(object):
+ def __getattr__(self, name):
+ if name[0] != "_":
+ return 42
+ raise AttributeError()
+
+ a = A()
+ he_pm.register(a)
+ assert not he_pm.get_hookcallers(a)
+
+
+def test_pm_name(pm):
+ class A(object):
+ pass
+
+ a1 = A()
+ name = pm.register(a1, name="hello")
+ assert name == "hello"
+ pm.unregister(a1)
+ assert pm.get_plugin(a1) is None
+ assert not pm.is_registered(a1)
+ assert not pm.get_plugins()
+ name2 = pm.register(a1, name="hello")
+ assert name2 == name
+ pm.unregister(name="hello")
+ assert pm.get_plugin(a1) is None
+ assert not pm.is_registered(a1)
+ assert not pm.get_plugins()
+
+
+def test_set_blocked(pm):
+ class A(object):
+ pass
+
+ a1 = A()
+ name = pm.register(a1)
+ assert pm.is_registered(a1)
+ assert not pm.is_blocked(name)
+ pm.set_blocked(name)
+ assert pm.is_blocked(name)
+ assert not pm.is_registered(a1)
+
+ pm.set_blocked("somename")
+ assert pm.is_blocked("somename")
+ assert not pm.register(A(), "somename")
+ pm.unregister(name="somename")
+ assert pm.is_blocked("somename")
+
+
+def test_register_mismatch_method(he_pm):
+ class hello(object):
+ @hookimpl
+ def he_method_notexists(self):
+ pass
+
+ he_pm.register(hello())
+ with pytest.raises(PluginValidationError):
+ he_pm.check_pending()
+
+
+def test_register_mismatch_arg(he_pm):
+ class hello(object):
+ @hookimpl
+ def he_method1(self, qlwkje):
+ pass
+
+ with pytest.raises(PluginValidationError):
+ he_pm.register(hello())
+
+
+def test_register(pm):
+ class MyPlugin(object):
+ pass
+ my = MyPlugin()
+ pm.register(my)
+ assert my in pm.get_plugins()
+ my2 = MyPlugin()
+ pm.register(my2)
+ assert set([my, my2]).issubset(pm.get_plugins())
+
+ assert pm.is_registered(my)
+ assert pm.is_registered(my2)
+ pm.unregister(my)
+ assert not pm.is_registered(my)
+ assert my not in pm.get_plugins()
+
+
+def test_register_unknown_hooks(pm):
+ class Plugin1(object):
+ @hookimpl
+ def he_method1(self, arg):
+ return arg + 1
+
+ pname = pm.register(Plugin1())
+
+ class Hooks(object):
+ @hookspec
+ def he_method1(self, arg):
+ pass
+
+ pm.add_hookspecs(Hooks)
+ # assert not pm._unverified_hooks
+ assert pm.hook.he_method1(arg=1) == [2]
+ assert len(pm.get_hookcallers(pm.get_plugin(pname))) == 1
+
+
+def test_register_historic(pm):
+ class Hooks(object):
+ @hookspec(historic=True)
+ def he_method1(self, arg):
+ pass
+ pm.add_hookspecs(Hooks)
+
+ pm.hook.he_method1.call_historic(kwargs=dict(arg=1))
+ out = []
+
+ class Plugin(object):
+ @hookimpl
+ def he_method1(self, arg):
+ out.append(arg)
+
+ pm.register(Plugin())
+ assert out == [1]
+
+ class Plugin2(object):
+ @hookimpl
+ def he_method1(self, arg):
+ out.append(arg * 10)
+
+ pm.register(Plugin2())
+ assert out == [1, 10]
+ pm.hook.he_method1.call_historic(kwargs=dict(arg=12))
+ assert out == [1, 10, 120, 12]
+
+
+def test_with_result_memorized(pm):
+ class Hooks(object):
+ @hookspec(historic=True)
+ def he_method1(self, arg):
+ pass
+ pm.add_hookspecs(Hooks)
+
+ he_method1 = pm.hook.he_method1
+ he_method1.call_historic(lambda res: out.append(res), dict(arg=1))
+ out = []
+
+ class Plugin(object):
+ @hookimpl
+ def he_method1(self, arg):
+ return arg * 10
+
+ pm.register(Plugin())
+ assert out == [10]
+
+
+def test_with_callbacks_immediately_executed(pm):
+ class Hooks(object):
+ @hookspec(historic=True)
+ def he_method1(self, arg):
+ pass
+ pm.add_hookspecs(Hooks)
+
+ class Plugin1(object):
+ @hookimpl
+ def he_method1(self, arg):
+ return arg * 10
+
+ class Plugin2(object):
+ @hookimpl
+ def he_method1(self, arg):
+ return arg * 20
+
+ class Plugin3(object):
+ @hookimpl
+ def he_method1(self, arg):
+ return arg * 30
+
+ out = []
+ pm.register(Plugin1())
+ pm.register(Plugin2())
+
+ he_method1 = pm.hook.he_method1
+ he_method1.call_historic(lambda res: out.append(res), dict(arg=1))
+ assert out == [20, 10]
+ pm.register(Plugin3())
+ assert out == [20, 10, 30]
+
+
+def test_register_historic_incompat_hookwrapper(pm):
+ class Hooks(object):
+ @hookspec(historic=True)
+ def he_method1(self, arg):
+ pass
+
+ pm.add_hookspecs(Hooks)
+
+ out = []
+
+ class Plugin(object):
+ @hookimpl(hookwrapper=True)
+ def he_method1(self, arg):
+ out.append(arg)
+
+ with pytest.raises(PluginValidationError):
+ pm.register(Plugin())
+
+
+def test_call_extra(pm):
+ class Hooks(object):
+ @hookspec
+ def he_method1(self, arg):
+ pass
+
+ pm.add_hookspecs(Hooks)
+
+ def he_method1(arg):
+ return arg * 10
+
+ out = pm.hook.he_method1.call_extra([he_method1], dict(arg=1))
+ assert out == [10]
+
+
+def test_call_with_too_few_args(pm):
+ class Hooks(object):
+ @hookspec
+ def he_method1(self, arg):
+ pass
+
+ pm.add_hookspecs(Hooks)
+
+ class Plugin1(object):
+ @hookimpl
+ def he_method1(self, arg):
+ 0 / 0
+ pm.register(Plugin1())
+ with pytest.raises(HookCallError):
+ with pytest.warns(UserWarning):
+ pm.hook.he_method1()
+
+
+def test_subset_hook_caller(pm):
+ class Hooks(object):
+ @hookspec
+ def he_method1(self, arg):
+ pass
+
+ pm.add_hookspecs(Hooks)
+
+ out = []
+
+ class Plugin1(object):
+ @hookimpl
+ def he_method1(self, arg):
+ out.append(arg)
+
+ class Plugin2(object):
+ @hookimpl
+ def he_method1(self, arg):
+ out.append(arg * 10)
+
+ class PluginNo(object):
+ pass
+
+ plugin1, plugin2, plugin3 = Plugin1(), Plugin2(), PluginNo()
+ pm.register(plugin1)
+ pm.register(plugin2)
+ pm.register(plugin3)
+ pm.hook.he_method1(arg=1)
+ assert out == [10, 1]
+ out[:] = []
+
+ hc = pm.subset_hook_caller("he_method1", [plugin1])
+ hc(arg=2)
+ assert out == [20]
+ out[:] = []
+
+ hc = pm.subset_hook_caller("he_method1", [plugin2])
+ hc(arg=2)
+ assert out == [2]
+ out[:] = []
+
+ pm.unregister(plugin1)
+ hc(arg=2)
+ assert out == []
+ out[:] = []
+
+ pm.hook.he_method1(arg=1)
+ assert out == [10]
+
+
+def test_multicall_deprecated(pm):
+ class P1(object):
+ @hookimpl
+ def m(self, __multicall__, x):
+ pass
+
+ pytest.deprecated_call(pm.register, P1())
+
+
+def test_add_hookspecs_nohooks(pm):
+ with pytest.raises(ValueError):
+ pm.add_hookspecs(10)
+
+
+def test_reject_prefixed_module(pm):
+ """Verify that a module type attribute that contains the project
+ prefix in its name (in this case `'example_*'` isn't collected
+ when registering a module which imports it.
+ """
+ pm._implprefix = 'example'
+ conftest = types.ModuleType("conftest")
+ src = ("""
+def example_hook():
+ pass
+""")
+ exec(src, conftest.__dict__)
+ conftest.example_blah = types.ModuleType("example_blah")
+ name = pm.register(conftest)
+ assert name == 'conftest'
+ assert getattr(pm.hook, 'example_blah', None) is None
+ assert getattr(pm.hook, 'example_hook', None) # conftest.example_hook should be collected
+ assert pm.parse_hookimpl_opts(conftest, 'example_blah') is None
+ assert pm.parse_hookimpl_opts(conftest, 'example_hook') == {}
diff --git a/third_party/python/pluggy/testing/test_tracer.py b/third_party/python/pluggy/testing/test_tracer.py
new file mode 100644
index 0000000000..4a3e16cec4
--- /dev/null
+++ b/third_party/python/pluggy/testing/test_tracer.py
@@ -0,0 +1,89 @@
+
+from pluggy import _TagTracer
+
+
+def test_simple():
+ rootlogger = _TagTracer()
+ log = rootlogger.get("pytest")
+ log("hello")
+ out = []
+ rootlogger.setwriter(out.append)
+ log("world")
+ assert len(out) == 1
+ assert out[0] == "world [pytest]\n"
+ sublog = log.get("collection")
+ sublog("hello")
+ assert out[1] == "hello [pytest:collection]\n"
+
+
+def test_indent():
+ rootlogger = _TagTracer()
+ log = rootlogger.get("1")
+ out = []
+ log.root.setwriter(lambda arg: out.append(arg))
+ log("hello")
+ log.root.indent += 1
+ log("line1")
+ log("line2")
+ log.root.indent += 1
+ log("line3")
+ log("line4")
+ log.root.indent -= 1
+ log("line5")
+ log.root.indent -= 1
+ log("last")
+ assert len(out) == 7
+ names = [x[:x.rfind(' [')] for x in out]
+ assert names == [
+ 'hello', ' line1', ' line2',
+ ' line3', ' line4', ' line5', 'last']
+
+
+def test_readable_output_dictargs():
+ rootlogger = _TagTracer()
+
+ out = rootlogger.format_message(['test'], [1])
+ assert out == ['1 [test]\n']
+
+ out2 = rootlogger.format_message(['test'], ['test', {'a': 1}])
+ assert out2 == [
+ 'test [test]\n',
+ ' a: 1\n'
+ ]
+
+
+def test_setprocessor():
+ rootlogger = _TagTracer()
+ log = rootlogger.get("1")
+ log2 = log.get("2")
+ assert log2.tags == tuple("12")
+ out = []
+ rootlogger.setprocessor(tuple("12"), lambda *args: out.append(args))
+ log("not seen")
+ log2("seen")
+ assert len(out) == 1
+ tags, args = out[0]
+ assert "1" in tags
+ assert "2" in tags
+ assert args == ("seen",)
+ l2 = []
+ rootlogger.setprocessor("1:2", lambda *args: l2.append(args))
+ log2("seen")
+ tags, args = l2[0]
+ assert args == ("seen",)
+
+
+def test_setmyprocessor():
+ rootlogger = _TagTracer()
+ log = rootlogger.get("1")
+ log2 = log.get("2")
+ out = []
+ log2.setmyprocessor(lambda *args: out.append(args))
+ log("not seen")
+ assert not out
+ log2(42)
+ assert len(out) == 1
+ tags, args = out[0]
+ assert "1" in tags
+ assert "2" in tags
+ assert args == (42,)