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)
|
||||
==================================================
|
||||
|
||||
- new mechanism to allow external plugins to register new hooks
|
||||
(a recent pytest-xdist plugin for distributed and looponfailing
|
||||
testing requires this feature)
|
||||
- allow external plugins to register new hooks via the new
|
||||
pytest_addhooks(pluginmanager) hook. The new release of
|
||||
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
|
||||
- fix/refine python3 compatibility (thanks Benjamin Peterson)
|
||||
- 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
|
||||
- (issue87) fix unboundlocal error in assertionold code
|
||||
- (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
|
||||
- ship distribute_setup.py version 0.6.10
|
||||
- added links to the new capturelog and coverage plugins
|
||||
|
|
|
@ -6,14 +6,14 @@ hook specifications for py.test plugins
|
|||
# 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():
|
||||
""" 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):
|
||||
""" called after command line options have been parsed.
|
||||
|
|
|
@ -21,4 +21,3 @@ def main(args=None):
|
|||
e = sys.exc_info()[1]
|
||||
sys.stderr.write("ERROR: %s\n" %(e.args[0],))
|
||||
raise SystemExit(3)
|
||||
|
||||
|
|
|
@ -39,8 +39,7 @@ class PluginManager(object):
|
|||
if name in self._name2plugin:
|
||||
return False
|
||||
self._name2plugin[name] = plugin
|
||||
self.call_plugin(plugin, "pytest_registerhooks",
|
||||
{'pluginmanager': self})
|
||||
self.call_plugin(plugin, "pytest_addhooks", {'pluginmanager': self})
|
||||
self.hook.pytest_plugin_registered(manager=self, plugin=plugin)
|
||||
self.registry.register(plugin)
|
||||
return True
|
||||
|
@ -59,8 +58,8 @@ class PluginManager(object):
|
|||
if plugin == val:
|
||||
return True
|
||||
|
||||
def registerhooks(self, spec):
|
||||
self.hook._registerhooks(spec)
|
||||
def addhooks(self, spec):
|
||||
self.hook._addhooks(spec, prefix="pytest_")
|
||||
|
||||
def getplugins(self):
|
||||
return list(self.registry)
|
||||
|
@ -304,22 +303,31 @@ class Registry:
|
|||
return l
|
||||
|
||||
class HookRelay:
|
||||
def __init__(self, hookspecs, registry):
|
||||
def __init__(self, hookspecs, registry, prefix="pytest_"):
|
||||
if not isinstance(hookspecs, list):
|
||||
hookspecs = [hookspecs]
|
||||
self._hookspecs = []
|
||||
self._registry = registry
|
||||
for hookspec in hookspecs:
|
||||
self._registerhooks(hookspec)
|
||||
self._addhooks(hookspec, prefix)
|
||||
|
||||
def _registerhooks(self, hookspecs):
|
||||
def _addhooks(self, hookspecs, prefix):
|
||||
self._hookspecs.append(hookspecs)
|
||||
added = False
|
||||
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)
|
||||
hc = HookCaller(self, name, firstresult=firstresult)
|
||||
setattr(self, name, hc)
|
||||
added = True
|
||||
#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):
|
||||
return multicall.execute()
|
||||
|
|
|
@ -202,6 +202,58 @@ class TestBootstrapping:
|
|||
impname = canonical_importname(name)
|
||||
|
||||
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):
|
||||
from py._test.config import Config
|
||||
p = testdir.makepyfile("""
|
||||
|
@ -401,9 +453,9 @@ class TestHookRelay:
|
|||
registry = Registry()
|
||||
class Api:
|
||||
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 repr(mcm.hello).find("hello") != -1
|
||||
class Plugin:
|
||||
|
@ -418,17 +470,18 @@ class TestHookRelay:
|
|||
registry = Registry()
|
||||
class Api:
|
||||
def hello(self, arg):
|
||||
pass
|
||||
mcm = HookRelay(hookspecs=Api, registry=registry)
|
||||
"api hook 1"
|
||||
mcm = HookRelay(hookspecs=Api, registry=registry, prefix="he")
|
||||
py.test.raises(TypeError, "mcm.hello(3)")
|
||||
|
||||
def test_firstresult_definition(self):
|
||||
registry = Registry()
|
||||
class Api:
|
||||
def hello(self, arg): pass
|
||||
def hello(self, arg):
|
||||
"api hook 1"
|
||||
hello.firstresult = True
|
||||
|
||||
mcm = HookRelay(hookspecs=Api, registry=registry)
|
||||
mcm = HookRelay(hookspecs=Api, registry=registry, prefix="he")
|
||||
class Plugin:
|
||||
def hello(self, arg):
|
||||
return arg + 1
|
||||
|
|
Loading…
Reference in New Issue