diff --git a/.travis.yml b/.travis.yml index 184d3d4d..42e174e9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,12 +7,8 @@ matrix: env: TOXENV=check - python: '3.6' env: TOXENV=docs - - python: '2.6' - env: TOXENV=py26-pytestrelease - python: '2.7' env: TOXENV=py27-pytestrelease - - python: '3.3' - env: TOXENV=py33-pytestrelease - python: '3.4' env: TOXENV=py34-pytestrelease - python: '3.5' diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a777b211..daa0f547 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,10 @@ +0.6.0 +----- + +- Drop support for EOL Python 2.6 and 3.3 in PR `#103`_. + +.. _#103: https://github.com/pytest-dev/pluggy/pull/103 + 0.5.2 ----- - fix bug where ``firstresult`` wrappers were being sent an incorrectly configured diff --git a/appveyor.yml b/appveyor.yml index c1b2fee2..c3903f8b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,7 +3,6 @@ environment: # note: please use "tox --listenvs" to populate the build matrix below - TOXENV: "check" - TOXENV: "docs" - - TOXENV: "py26-pytestrelease" - TOXENV: "py27-pytestrelease" - TOXENV: "py34-pytestrelease" - TOXENV: "py35-pytestrelease" diff --git a/pluggy/__init__.py b/pluggy/__init__.py index 40dd02f9..ab5175d9 100644 --- a/pluggy/__init__.py +++ b/pluggy/__init__.py @@ -212,7 +212,8 @@ def __init__(self, project_name, implprefix=None): self._implprefix = implprefix self._inner_hookexec = lambda hook, methods, kwargs: \ hook.multicall( - methods, kwargs, specopts=hook.spec_opts, hook=hook + methods, kwargs, + firstresult=hook.spec_opts.get('firstresult'), ) def _hookexec(self, hook, methods, kwargs): @@ -528,20 +529,22 @@ def __init__(self, trace): class _HookCaller(object): - def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None): + def __init__(self, name, hook_execute, specmodule_or_class=None, + spec_opts=None): self.name = name self._wrappers = [] self._nonwrappers = [] self._hookexec = hook_execute + self._specmodule_or_class = None self.argnames = None self.kwargnames = None self.multicall = _multicall + self.spec_opts = spec_opts or {} if specmodule_or_class is not None: - assert spec_opts is not None self.set_specification(specmodule_or_class, spec_opts) def has_spec(self): - return hasattr(self, "_specmodule_or_class") + return self._specmodule_or_class is not None def set_specification(self, specmodule_or_class, spec_opts): assert not self.has_spec() @@ -550,7 +553,7 @@ def set_specification(self, specmodule_or_class, spec_opts): # get spec arg signature argnames, self.kwargnames = varnames(specfunc) self.argnames = ["__multicall__"] + list(argnames) - self.spec_opts = spec_opts + self.spec_opts.update(spec_opts) if spec_opts.get("historic"): self._call_history = [] @@ -606,7 +609,7 @@ def __call__(self, *args, **kwargs): kwargs.keys()) if notincall: warnings.warn( - "Argument(s) {0} which are declared in the hookspec " + "Argument(s) {} which are declared in the hookspec " "can not be found in this hook call" .format(tuple(notincall)), stacklevel=2, diff --git a/pluggy/callers.py b/pluggy/callers.py index 3189f8aa..3ff67bec 100644 --- a/pluggy/callers.py +++ b/pluggy/callers.py @@ -105,17 +105,16 @@ class _LegacyMultiCall(object): # so we can remove it soon, allowing to avoid the below recursion # in execute() and simplify/speed up the execute loop. - def __init__(self, hook_impls, kwargs, specopts={}, hook=None): - self.hook = hook + def __init__(self, hook_impls, kwargs, firstresult=False): self.hook_impls = hook_impls self.caller_kwargs = kwargs # come from _HookCaller.__call__() self.caller_kwargs["__multicall__"] = self - self.specopts = hook.spec_opts if hook else specopts + self.firstresult = firstresult def execute(self): caller_kwargs = self.caller_kwargs self.results = results = [] - firstresult = self.specopts.get("firstresult") + firstresult = self.firstresult while self.hook_impls: hook_impl = self.hook_impls.pop() @@ -144,21 +143,19 @@ def __repr__(self): return "<_MultiCall %s, kwargs=%r>" % (status, self.caller_kwargs) -def _legacymulticall(hook_impls, caller_kwargs, specopts={}, hook=None): +def _legacymulticall(hook_impls, caller_kwargs, firstresult=False): return _LegacyMultiCall( - hook_impls, caller_kwargs, specopts=specopts, hook=hook).execute() + hook_impls, caller_kwargs, firstresult=firstresult).execute() -def _multicall(hook_impls, caller_kwargs, specopts={}, hook=None): +def _multicall(hook_impls, caller_kwargs, firstresult=False): """Execute a call into multiple python functions/methods and return the result(s). ``caller_kwargs`` comes from _HookCaller.__call__(). """ __tracebackhide__ = True - specopts = hook.spec_opts if hook else specopts results = [] - firstresult = specopts.get("firstresult") excinfo = None try: # run impl and wrapper setup functions in a loop teardowns = [] diff --git a/setup.py b/setup.py index c8a72607..4774b2e1 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy'] + [ ('Programming Language :: Python :: %s' % x) for x in - '2 2.6 2.7 3 3.3 3.4 3.5 3.6'.split()] + '2 2.7 3 3.4 3.5 3.6'.split()] with open('README.rst') as fd: long_description = fd.read() diff --git a/testing/test_hookrelay.py b/testing/test_hookrelay.py index a3b14783..5e7821be 100644 --- a/testing/test_hookrelay.py +++ b/testing/test_hookrelay.py @@ -24,8 +24,8 @@ def hello(self, arg): plugin = Plugin() pm.register(plugin) - l = hook.hello(arg=3) - assert l == [4] + out = hook.hello(arg=3) + assert out == [4] assert not hasattr(hook, 'world') pm.unregister(plugin) assert hook.hello(arg=3) == [] diff --git a/testing/test_method_ordering.py b/testing/test_method_ordering.py index 8c3121dc..9584a0ae 100644 --- a/testing/test_method_ordering.py +++ b/testing/test_method_ordering.py @@ -215,42 +215,42 @@ def test_load_setuptools_not_installed(monkeypatch, pm): def test_add_tracefuncs(he_pm): - l = [] + out = [] class api1(object): @hookimpl def he_method1(self): - l.append("he_method1-api1") + out.append("he_method1-api1") class api2(object): @hookimpl def he_method1(self): - l.append("he_method1-api2") + out.append("he_method1-api2") he_pm.register(api1()) he_pm.register(api2()) def before(hook_name, hook_impls, kwargs): - l.append((hook_name, list(hook_impls), kwargs)) + out.append((hook_name, list(hook_impls), kwargs)) def after(outcome, hook_name, hook_impls, kwargs): - l.append((outcome, hook_name, list(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(l) == 4 - assert l[0][0] == "he_method1" - assert len(l[0][1]) == 2 - assert isinstance(l[0][2], dict) - assert l[1] == "he_method1-api2" - assert l[2] == "he_method1-api1" - assert len(l[3]) == 4 - assert l[3][1] == l[0][0] + 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(l) == 4 + 2 + assert len(out) == 4 + 2 def test_hook_tracing(he_pm): @@ -268,18 +268,18 @@ def he_method1(self): raise ValueError() he_pm.register(api1()) - l = [] - he_pm.trace.root.setwriter(l.append) + 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(l) == 2 - assert 'he_method1' in l[0] - assert 'finish' in l[1] + assert len(out) == 2 + assert 'he_method1' in out[0] + assert 'finish' in out[1] - l[:] = [] + out[:] = [] he_pm.register(api2()) with pytest.raises(ValueError): @@ -290,15 +290,17 @@ def he_method1(self): undo() -def test_prefix_hookimpl(): +@pytest.mark.parametrize('include_hookspec', [True, False]) +def test_prefix_hookimpl(include_hookspec): pm = PluginManager(hookspec.project_name, "hello_") - class HookSpec(object): - @hookspec - def hello_myhook(self, arg1): - """ add to arg1 """ + if include_hookspec: + class HookSpec(object): + @hookspec + def hello_myhook(self, arg1): + """ add to arg1 """ - pm.add_hookspecs(HookSpec) + pm.add_hookspecs(HookSpec) class Plugin(object): def hello_myhook(self, arg1): diff --git a/testing/test_multicall.py b/testing/test_multicall.py index bbc7921f..860a209b 100644 --- a/testing/test_multicall.py +++ b/testing/test_multicall.py @@ -10,10 +10,10 @@ def test_uses_copy_of_methods(): - l = [lambda: 42] - mc = _LegacyMultiCall(l, {}) + out = [lambda: 42] + mc = _LegacyMultiCall(out, {}) repr(mc) - l[:] = [] + out[:] = [] res = mc.execute() return res == 42 @@ -26,7 +26,7 @@ def MC(methods, kwargs, firstresult=False): hookfuncs.append(f) if '__multicall__' in f.argnames: caller = _legacymulticall - return caller(hookfuncs, kwargs, specopts={"firstresult": firstresult}) + return caller(hookfuncs, kwargs, firstresult=firstresult) def test_call_passing(): @@ -105,53 +105,53 @@ def m1(): def m2(): return None - res = MC([m1, m2], {}, {"firstresult": True}) + res = MC([m1, m2], {}, firstresult=True) assert res == 1 res = MC([m1, m2], {}, {}) assert res == [1] def test_hookwrapper(): - l = [] + out = [] @hookimpl(hookwrapper=True) def m1(): - l.append("m1 init") + out.append("m1 init") yield None - l.append("m1 finish") + out.append("m1 finish") @hookimpl def m2(): - l.append("m2") + out.append("m2") return 2 res = MC([m2, m1], {}) assert res == [2] - assert l == ["m1 init", "m2", "m1 finish"] - l[:] = [] - res = MC([m2, m1], {}, {"firstresult": True}) + assert out == ["m1 init", "m2", "m1 finish"] + out[:] = [] + res = MC([m2, m1], {}, firstresult=True) assert res == 2 - assert l == ["m1 init", "m2", "m1 finish"] + assert out == ["m1 init", "m2", "m1 finish"] def test_hookwrapper_order(): - l = [] + out = [] @hookimpl(hookwrapper=True) def m1(): - l.append("m1 init") + out.append("m1 init") yield 1 - l.append("m1 finish") + out.append("m1 finish") @hookimpl(hookwrapper=True) def m2(): - l.append("m2 init") + out.append("m2 init") yield 2 - l.append("m2 finish") + out.append("m2 finish") res = MC([m2, m1], {}) assert res == [] - assert l == ["m1 init", "m2 init", "m2 finish", "m1 finish"] + assert out == ["m1 init", "m2 init", "m2 finish", "m1 finish"] def test_hookwrapper_not_yield(): @@ -177,13 +177,13 @@ def m1(): @pytest.mark.parametrize("exc", [ValueError, SystemExit]) def test_hookwrapper_exception(exc): - l = [] + out = [] @hookimpl(hookwrapper=True) def m1(): - l.append("m1 init") + out.append("m1 init") yield None - l.append("m1 finish") + out.append("m1 finish") @hookimpl def m2(): @@ -191,4 +191,4 @@ def m2(): with pytest.raises(exc): MC([m2, m1], {}) - assert l == ["m1 init", "m1 finish"] + assert out == ["m1 init", "m1 finish"] diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index 7e9701b3..e2c86cc6 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -1,4 +1,5 @@ import pytest +import types from pluggy import (PluginValidationError, HookCallError, HookimplMarker, HookspecMarker) @@ -25,16 +26,16 @@ class A(object): assert pm.is_registered(a1) pm.register(a2, "hello") assert pm.is_registered(a2) - l = pm.get_plugins() - assert a1 in l - assert a2 in l + 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) - l = pm.list_name_plugin() - assert len(l) == 1 - assert l == [("hello", a2)] + out = pm.list_name_plugin() + assert len(out) == 1 + assert out == [("hello", a2)] def test_has_plugin(pm): @@ -162,25 +163,25 @@ def he_method1(self, arg): pm.add_hookspecs(Hooks) pm.hook.he_method1.call_historic(kwargs=dict(arg=1)) - l = [] + out = [] class Plugin(object): @hookimpl def he_method1(self, arg): - l.append(arg) + out.append(arg) pm.register(Plugin()) - assert l == [1] + assert out == [1] class Plugin2(object): @hookimpl def he_method1(self, arg): - l.append(arg * 10) + out.append(arg * 10) pm.register(Plugin2()) - assert l == [1, 10] + assert out == [1, 10] pm.hook.he_method1.call_historic(kwargs=dict(arg=12)) - assert l == [1, 10, 120, 12] + assert out == [1, 10, 120, 12] def test_with_result_memorized(pm): @@ -191,8 +192,8 @@ def he_method1(self, arg): pm.add_hookspecs(Hooks) he_method1 = pm.hook.he_method1 - he_method1.call_historic(lambda res: l.append(res), dict(arg=1)) - l = [] + he_method1.call_historic(lambda res: out.append(res), dict(arg=1)) + out = [] class Plugin(object): @hookimpl @@ -200,7 +201,7 @@ def he_method1(self, arg): return arg * 10 pm.register(Plugin()) - assert l == [10] + assert out == [10] def test_with_callbacks_immediately_executed(pm): @@ -225,15 +226,15 @@ class Plugin3(object): def he_method1(self, arg): return arg * 30 - l = [] + out = [] pm.register(Plugin1()) pm.register(Plugin2()) he_method1 = pm.hook.he_method1 - he_method1.call_historic(lambda res: l.append(res), dict(arg=1)) - assert l == [20, 10] + he_method1.call_historic(lambda res: out.append(res), dict(arg=1)) + assert out == [20, 10] pm.register(Plugin3()) - assert l == [20, 10, 30] + assert out == [20, 10, 30] def test_register_historic_incompat_hookwrapper(pm): @@ -244,12 +245,12 @@ def he_method1(self, arg): pm.add_hookspecs(Hooks) - l = [] + out = [] class Plugin(object): @hookimpl(hookwrapper=True) def he_method1(self, arg): - l.append(arg) + out.append(arg) with pytest.raises(PluginValidationError): pm.register(Plugin()) @@ -266,8 +267,8 @@ def he_method1(self, arg): def he_method1(arg): return arg * 10 - l = pm.hook.he_method1.call_extra([he_method1], dict(arg=1)) - assert l == [10] + out = pm.hook.he_method1.call_extra([he_method1], dict(arg=1)) + assert out == [10] def test_call_with_too_few_args(pm): @@ -296,17 +297,17 @@ def he_method1(self, arg): pm.add_hookspecs(Hooks) - l = [] + out = [] class Plugin1(object): @hookimpl def he_method1(self, arg): - l.append(arg) + out.append(arg) class Plugin2(object): @hookimpl def he_method1(self, arg): - l.append(arg * 10) + out.append(arg * 10) class PluginNo(object): pass @@ -316,26 +317,26 @@ class PluginNo(object): pm.register(plugin2) pm.register(plugin3) pm.hook.he_method1(arg=1) - assert l == [10, 1] - l[:] = [] + assert out == [10, 1] + out[:] = [] hc = pm.subset_hook_caller("he_method1", [plugin1]) hc(arg=2) - assert l == [20] - l[:] = [] + assert out == [20] + out[:] = [] hc = pm.subset_hook_caller("he_method1", [plugin2]) hc(arg=2) - assert l == [2] - l[:] = [] + assert out == [2] + out[:] = [] pm.unregister(plugin1) hc(arg=2) - assert l == [] - l[:] = [] + assert out == [] + out[:] = [] pm.hook.he_method1(arg=1) - assert l == [10] + assert out == [10] def test_multicall_deprecated(pm): @@ -350,3 +351,24 @@ def m(self, __multicall__, x): 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/testing/test_tracer.py b/testing/test_tracer.py index 852e5c19..4a3e16ce 100644 --- a/testing/test_tracer.py +++ b/testing/test_tracer.py @@ -6,21 +6,21 @@ def test_simple(): rootlogger = _TagTracer() log = rootlogger.get("pytest") log("hello") - l = [] - rootlogger.setwriter(l.append) + out = [] + rootlogger.setwriter(out.append) log("world") - assert len(l) == 1 - assert l[0] == "world [pytest]\n" + assert len(out) == 1 + assert out[0] == "world [pytest]\n" sublog = log.get("collection") sublog("hello") - assert l[1] == "hello [pytest:collection]\n" + assert out[1] == "hello [pytest:collection]\n" def test_indent(): rootlogger = _TagTracer() log = rootlogger.get("1") - l = [] - log.root.setwriter(lambda arg: l.append(arg)) + out = [] + log.root.setwriter(lambda arg: out.append(arg)) log("hello") log.root.indent += 1 log("line1") @@ -32,8 +32,8 @@ def test_indent(): log("line5") log.root.indent -= 1 log("last") - assert len(l) == 7 - names = [x[:x.rfind(' [')] for x in l] + assert len(out) == 7 + names = [x[:x.rfind(' [')] for x in out] assert names == [ 'hello', ' line1', ' line2', ' line3', ' line4', ' line5', 'last'] @@ -57,12 +57,12 @@ def test_setprocessor(): log = rootlogger.get("1") log2 = log.get("2") assert log2.tags == tuple("12") - l = [] - rootlogger.setprocessor(tuple("12"), lambda *args: l.append(args)) + out = [] + rootlogger.setprocessor(tuple("12"), lambda *args: out.append(args)) log("not seen") log2("seen") - assert len(l) == 1 - tags, args = l[0] + assert len(out) == 1 + tags, args = out[0] assert "1" in tags assert "2" in tags assert args == ("seen",) @@ -77,13 +77,13 @@ def test_setmyprocessor(): rootlogger = _TagTracer() log = rootlogger.get("1") log2 = log.get("2") - l = [] - log2.setmyprocessor(lambda *args: l.append(args)) + out = [] + log2.setmyprocessor(lambda *args: out.append(args)) log("not seen") - assert not l + assert not out log2(42) - assert len(l) == 1 - tags, args = l[0] + assert len(out) == 1 + tags, args = out[0] assert "1" in tags assert "2" in tags assert args == (42,) diff --git a/tox.ini b/tox.ini index c7a3587c..89d44e35 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=check,docs,py{26,27,34,35,36,py}-pytestrelease,py{27,36}-pytest{master,features} +envlist=check,docs,py{27,34,35,36,py}-pytestrelease,py{27,36}-pytest{master,features} [testenv] commands=py.test {posargs:testing/}