refine and test new hook registration, now it is called "pytest_addhooks"
similar to pytest_addoption and raises on bogus input. --HG-- branch : trunk
This commit is contained in:
parent
45e10f4c48
commit
fd473d4002
39
CHANGELOG
39
CHANGELOG
|
@ -1,9 +1,26 @@
|
||||||
Changes between 1.2.1 and 1.3.0 (release pending)
|
Changes between 1.2.1 and 1.3.0 (release pending)
|
||||||
==================================================
|
==================================================
|
||||||
|
|
||||||
- new mechanism to allow external plugins to register new hooks
|
- allow external plugins to register new hooks via the new
|
||||||
(a recent pytest-xdist plugin for distributed and looponfailing
|
pytest_addhooks(pluginmanager) hook. The new release of
|
||||||
testing requires this feature)
|
the pytest-xdist plugin for distributed and looponfailing
|
||||||
|
testing requires this feature.
|
||||||
|
- add a new pytest_ignore_collect(path, config) hook to allow projects and
|
||||||
|
plugins to define exclusion behaviour for their directory structure -
|
||||||
|
for example you may define in a conftest.py this method:
|
||||||
|
def pytest_ignore_collect(path):
|
||||||
|
return path.check(link=1)
|
||||||
|
to prevent even a collection try of any tests in symlinked dirs.
|
||||||
|
- new pytest_pycollect_makemodule(path, parent) hook for
|
||||||
|
allowing customization of the Module collection object for a
|
||||||
|
matching test module.
|
||||||
|
- expose (previously internal) commonly useful methods:
|
||||||
|
py.io.get_terminal_with() -> return terminal width
|
||||||
|
py.io.ansi_print(...) -> print colored/bold text on linux/win32
|
||||||
|
py.io.saferepr(obj) -> return limited representation string
|
||||||
|
- expose test outcome related exceptions as py.test.skip.Exception,
|
||||||
|
py.test.raises.Exception etc., useful mostly for plugins
|
||||||
|
doing special outcome interpretation/tweaking
|
||||||
- (issue85) fix junitxml plugin to handle tests with non-ascii output
|
- (issue85) fix junitxml plugin to handle tests with non-ascii output
|
||||||
- fix/refine python3 compatibility (thanks Benjamin Peterson)
|
- fix/refine python3 compatibility (thanks Benjamin Peterson)
|
||||||
- fixes for making the jython/win32 combination work, note however:
|
- fixes for making the jython/win32 combination work, note however:
|
||||||
|
@ -13,22 +30,6 @@ Changes between 1.2.1 and 1.3.0 (release pending)
|
||||||
- fixes for handling of unicode exception values and unprintable objects
|
- fixes for handling of unicode exception values and unprintable objects
|
||||||
- (issue87) fix unboundlocal error in assertionold code
|
- (issue87) fix unboundlocal error in assertionold code
|
||||||
- (issue86) improve documentation for looponfailing
|
- (issue86) improve documentation for looponfailing
|
||||||
- add a new pytest_ignore_collect(path, config) hook to allow projects and
|
|
||||||
plugins to define exclusion behaviour for their directory structure -
|
|
||||||
for example you may define in a conftest.py this method:
|
|
||||||
def pytest_ignore_collect(path):
|
|
||||||
return path.check(link=1)
|
|
||||||
to prevent even a collection try of any tests in symlinked dirs.
|
|
||||||
- new pytest_pycollect_makemodule(path, parent) hook for
|
|
||||||
allowing customization of the Module collection object for a
|
|
||||||
matching test module.
|
|
||||||
- expose (previously internal) commonly useful methods:
|
|
||||||
py.io.get_terminal_with() -> return terminal width
|
|
||||||
py.io.ansi_print(...) -> print colored/bold text on linux/win32
|
|
||||||
py.io.saferepr(obj) -> return limited representation string
|
|
||||||
- expose test outcome related exceptions as py.test.skip.Exception,
|
|
||||||
py.test.raises.Exception etc., useful mostly for plugins
|
|
||||||
doing special outcome interpretation/tweaking
|
|
||||||
- refine IO capturing: stdin-redirect pseudo-file now has a NOP close() method
|
- refine IO capturing: stdin-redirect pseudo-file now has a NOP close() method
|
||||||
- ship distribute_setup.py version 0.6.10
|
- ship distribute_setup.py version 0.6.10
|
||||||
- added links to the new capturelog and coverage plugins
|
- added links to the new capturelog and coverage plugins
|
||||||
|
|
|
@ -6,14 +6,14 @@ hook specifications for py.test plugins
|
||||||
# Command line and configuration
|
# Command line and configuration
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
def pytest_addoption(parser):
|
|
||||||
""" called before commandline parsing. """
|
|
||||||
|
|
||||||
def pytest_registerhooks(pluginmanager):
|
|
||||||
""" called after commandline parsing before pytest_configure. """
|
|
||||||
|
|
||||||
def pytest_namespace():
|
def pytest_namespace():
|
||||||
""" return dict of name->object which will get stored at py.test. namespace"""
|
"return dict of name->object which will get stored at py.test. namespace"
|
||||||
|
|
||||||
|
def pytest_addoption(parser):
|
||||||
|
"add optparse-style options via parser.addoption."
|
||||||
|
|
||||||
|
def pytest_addhooks(pluginmanager):
|
||||||
|
"add hooks via pluginmanager.registerhooks(module)"
|
||||||
|
|
||||||
def pytest_configure(config):
|
def pytest_configure(config):
|
||||||
""" called after command line options have been parsed.
|
""" called after command line options have been parsed.
|
||||||
|
|
|
@ -21,4 +21,3 @@ def main(args=None):
|
||||||
e = sys.exc_info()[1]
|
e = sys.exc_info()[1]
|
||||||
sys.stderr.write("ERROR: %s\n" %(e.args[0],))
|
sys.stderr.write("ERROR: %s\n" %(e.args[0],))
|
||||||
raise SystemExit(3)
|
raise SystemExit(3)
|
||||||
|
|
||||||
|
|
|
@ -39,8 +39,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.call_plugin(plugin, "pytest_registerhooks",
|
self.call_plugin(plugin, "pytest_addhooks", {'pluginmanager': self})
|
||||||
{'pluginmanager': self})
|
|
||||||
self.hook.pytest_plugin_registered(manager=self, plugin=plugin)
|
self.hook.pytest_plugin_registered(manager=self, plugin=plugin)
|
||||||
self.registry.register(plugin)
|
self.registry.register(plugin)
|
||||||
return True
|
return True
|
||||||
|
@ -59,8 +58,8 @@ class PluginManager(object):
|
||||||
if plugin == val:
|
if plugin == val:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def registerhooks(self, spec):
|
def addhooks(self, spec):
|
||||||
self.hook._registerhooks(spec)
|
self.hook._addhooks(spec, prefix="pytest_")
|
||||||
|
|
||||||
def getplugins(self):
|
def getplugins(self):
|
||||||
return list(self.registry)
|
return list(self.registry)
|
||||||
|
@ -304,22 +303,31 @@ class Registry:
|
||||||
return l
|
return l
|
||||||
|
|
||||||
class HookRelay:
|
class HookRelay:
|
||||||
def __init__(self, hookspecs, registry):
|
def __init__(self, hookspecs, registry, prefix="pytest_"):
|
||||||
if not isinstance(hookspecs, list):
|
if not isinstance(hookspecs, list):
|
||||||
hookspecs = [hookspecs]
|
hookspecs = [hookspecs]
|
||||||
self._hookspecs = []
|
self._hookspecs = []
|
||||||
self._registry = registry
|
self._registry = registry
|
||||||
for hookspec in hookspecs:
|
for hookspec in hookspecs:
|
||||||
self._registerhooks(hookspec)
|
self._addhooks(hookspec, prefix)
|
||||||
|
|
||||||
def _registerhooks(self, hookspecs):
|
def _addhooks(self, hookspecs, prefix):
|
||||||
self._hookspecs.append(hookspecs)
|
self._hookspecs.append(hookspecs)
|
||||||
|
added = False
|
||||||
for name, method in vars(hookspecs).items():
|
for name, method in vars(hookspecs).items():
|
||||||
if name[:1] != "_":
|
if name.startswith(prefix):
|
||||||
|
if not method.__doc__:
|
||||||
|
raise ValueError("docstring required for hook %r, in %r"
|
||||||
|
% (method, hookspecs))
|
||||||
firstresult = getattr(method, 'firstresult', False)
|
firstresult = getattr(method, 'firstresult', False)
|
||||||
hc = HookCaller(self, name, firstresult=firstresult)
|
hc = HookCaller(self, name, firstresult=firstresult)
|
||||||
setattr(self, name, hc)
|
setattr(self, name, hc)
|
||||||
|
added = True
|
||||||
#print ("setting new hook", name)
|
#print ("setting new hook", name)
|
||||||
|
if not added:
|
||||||
|
raise ValueError("did not find new %r hooks in %r" %(
|
||||||
|
prefix, hookspecs,))
|
||||||
|
|
||||||
|
|
||||||
def _performcall(self, name, multicall):
|
def _performcall(self, name, multicall):
|
||||||
return multicall.execute()
|
return multicall.execute()
|
||||||
|
|
|
@ -202,6 +202,58 @@ class TestBootstrapping:
|
||||||
impname = canonical_importname(name)
|
impname = canonical_importname(name)
|
||||||
|
|
||||||
class TestPytestPluginInteractions:
|
class TestPytestPluginInteractions:
|
||||||
|
|
||||||
|
def test_addhooks_conftestplugin(self, testdir):
|
||||||
|
from py._test.config import Config
|
||||||
|
newhooks = testdir.makepyfile(newhooks="""
|
||||||
|
def pytest_myhook(xyz):
|
||||||
|
"new hook"
|
||||||
|
""")
|
||||||
|
conf = testdir.makeconftest("""
|
||||||
|
import sys ; sys.path.insert(0, '.')
|
||||||
|
import newhooks
|
||||||
|
def pytest_addhooks(pluginmanager):
|
||||||
|
pluginmanager.addhooks(newhooks)
|
||||||
|
def pytest_myhook(xyz):
|
||||||
|
return xyz + 1
|
||||||
|
""")
|
||||||
|
config = Config()
|
||||||
|
config._conftest.importconftest(conf)
|
||||||
|
print(config.pluginmanager.getplugins())
|
||||||
|
res = config.hook.pytest_myhook(xyz=10)
|
||||||
|
assert res == [11]
|
||||||
|
|
||||||
|
def test_addhooks_docstring_error(self, testdir):
|
||||||
|
newhooks = testdir.makepyfile(newhooks="""
|
||||||
|
class A: # no pytest_ prefix
|
||||||
|
pass
|
||||||
|
def pytest_myhook(xyz):
|
||||||
|
pass
|
||||||
|
""")
|
||||||
|
conf = testdir.makeconftest("""
|
||||||
|
import sys ; sys.path.insert(0, '.')
|
||||||
|
import newhooks
|
||||||
|
def pytest_addhooks(pluginmanager):
|
||||||
|
pluginmanager.addhooks(newhooks)
|
||||||
|
""")
|
||||||
|
res = testdir.runpytest()
|
||||||
|
assert res.ret != 0
|
||||||
|
res.stderr.fnmatch_lines([
|
||||||
|
"*docstring*pytest_myhook*newhooks*"
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_addhooks_nohooks(self, testdir):
|
||||||
|
conf = testdir.makeconftest("""
|
||||||
|
import sys
|
||||||
|
def pytest_addhooks(pluginmanager):
|
||||||
|
pluginmanager.addhooks(sys)
|
||||||
|
""")
|
||||||
|
res = testdir.runpytest()
|
||||||
|
assert res.ret != 0
|
||||||
|
res.stderr.fnmatch_lines([
|
||||||
|
"*did not find*sys*"
|
||||||
|
])
|
||||||
|
|
||||||
def test_do_option_conftestplugin(self, testdir):
|
def test_do_option_conftestplugin(self, testdir):
|
||||||
from py._test.config import Config
|
from py._test.config import Config
|
||||||
p = testdir.makepyfile("""
|
p = testdir.makepyfile("""
|
||||||
|
@ -401,9 +453,9 @@ class TestHookRelay:
|
||||||
registry = Registry()
|
registry = Registry()
|
||||||
class Api:
|
class Api:
|
||||||
def hello(self, arg):
|
def hello(self, arg):
|
||||||
pass
|
"api hook 1"
|
||||||
|
|
||||||
mcm = HookRelay(hookspecs=Api, registry=registry)
|
mcm = HookRelay(hookspecs=Api, registry=registry, prefix="he")
|
||||||
assert hasattr(mcm, 'hello')
|
assert hasattr(mcm, 'hello')
|
||||||
assert repr(mcm.hello).find("hello") != -1
|
assert repr(mcm.hello).find("hello") != -1
|
||||||
class Plugin:
|
class Plugin:
|
||||||
|
@ -418,17 +470,18 @@ class TestHookRelay:
|
||||||
registry = Registry()
|
registry = Registry()
|
||||||
class Api:
|
class Api:
|
||||||
def hello(self, arg):
|
def hello(self, arg):
|
||||||
pass
|
"api hook 1"
|
||||||
mcm = HookRelay(hookspecs=Api, registry=registry)
|
mcm = HookRelay(hookspecs=Api, registry=registry, prefix="he")
|
||||||
py.test.raises(TypeError, "mcm.hello(3)")
|
py.test.raises(TypeError, "mcm.hello(3)")
|
||||||
|
|
||||||
def test_firstresult_definition(self):
|
def test_firstresult_definition(self):
|
||||||
registry = Registry()
|
registry = Registry()
|
||||||
class Api:
|
class Api:
|
||||||
def hello(self, arg): pass
|
def hello(self, arg):
|
||||||
|
"api hook 1"
|
||||||
hello.firstresult = True
|
hello.firstresult = True
|
||||||
|
|
||||||
mcm = HookRelay(hookspecs=Api, registry=registry)
|
mcm = HookRelay(hookspecs=Api, registry=registry, prefix="he")
|
||||||
class Plugin:
|
class Plugin:
|
||||||
def hello(self, arg):
|
def hello(self, arg):
|
||||||
return arg + 1
|
return arg + 1
|
||||||
|
|
Loading…
Reference in New Issue