merge pytest default

This commit is contained in:
holger krekel 2014-10-07 18:11:15 +02:00
commit b6e619413f
12 changed files with 446 additions and 416 deletions

View File

@ -98,7 +98,7 @@ class PytestPluginManager(PluginManager):
err = py.io.dupfile(err, encoding=encoding) err = py.io.dupfile(err, encoding=encoding)
except Exception: except Exception:
pass pass
self.trace.root.setwriter(err.write) self.set_tracing(err.write)
def pytest_configure(self, config): def pytest_configure(self, config):
config.addinivalue_line("markers", config.addinivalue_line("markers",
@ -682,11 +682,8 @@ class Config(object):
setattr(self.option, opt.dest, opt.default) setattr(self.option, opt.dest, opt.default)
def _getmatchingplugins(self, fspath): def _getmatchingplugins(self, fspath):
allconftests = self._conftest._conftestpath2mod.values() return self.pluginmanager._plugins + \
plugins = [x for x in self.pluginmanager.getplugins() self._conftest.getconftestmodules(fspath)
if x not in allconftests]
plugins += self._conftest.getconftestmodules(fspath)
return plugins
def pytest_load_initial_conftests(self, early_config): def pytest_load_initial_conftests(self, early_config):
self._conftest.setinitial(early_config.known_args_namespace) self._conftest.setinitial(early_config.known_args_namespace)

View File

@ -67,16 +67,74 @@ class TagTracerSub:
def get(self, name): def get(self, name):
return self.__class__(self.root, self.tags + (name,)) return self.__class__(self.root, self.tags + (name,))
def add_method_controller(cls, func):
""" Use func as the method controler for the method found
at the class named func.__name__.
A method controler is invoked with the same arguments
as the function it substitutes and is required to yield once
which will trigger calling the controlled method.
If it yields a second value, the value will be returned
as the result of the invocation. Errors in the controlled function
are re-raised to the controller during the first yield.
"""
name = func.__name__
oldcall = getattr(cls, name)
def wrap_exec(*args, **kwargs):
gen = func(*args, **kwargs)
next(gen) # first yield
try:
res = oldcall(*args, **kwargs)
except Exception:
excinfo = sys.exc_info()
try:
# reraise exception to controller
res = gen.throw(*excinfo)
except StopIteration:
py.builtin._reraise(*excinfo)
else:
try:
res = gen.send(res)
except StopIteration:
pass
return res
setattr(cls, name, wrap_exec)
return lambda: setattr(cls, name, oldcall)
class PluginManager(object): class PluginManager(object):
def __init__(self, hookspecs=None): def __init__(self, hookspecs=None, prefix="pytest_"):
self._name2plugin = {} self._name2plugin = {}
self._listattrcache = {}
self._plugins = [] self._plugins = []
self._conftestplugins = []
self._warnings = [] self._warnings = []
self.trace = TagTracer().get("pluginmanage") self.trace = TagTracer().get("pluginmanage")
self._plugin_distinfo = [] self._plugin_distinfo = []
self._shutdown = [] self._shutdown = []
self.hook = HookRelay(hookspecs or [], pm=self) self.hook = HookRelay(hookspecs or [], pm=self, prefix=prefix)
def set_tracing(self, writer):
self.trace.root.setwriter(writer)
# reconfigure HookCalling to perform tracing
assert not hasattr(self, "_wrapping")
self._wrapping = True
def _docall(self, methods, kwargs):
trace = self.hookrelay.trace
trace.root.indent += 1
trace(self.name, kwargs)
res = None
try:
res = yield
finally:
if res:
trace("finish", self.name, "-->", res)
trace.root.indent -= 1
undo = add_method_controller(HookCaller, _docall)
self.add_shutdown(undo)
def do_configure(self, config): def do_configure(self, config):
# backward compatibility # backward compatibility
@ -86,7 +144,7 @@ class PluginManager(object):
assert not hasattr(self, "_registercallback") assert not hasattr(self, "_registercallback")
self._registercallback = callback self._registercallback = callback
def register(self, plugin, name=None, prepend=False): def register(self, plugin, name=None, prepend=False, conftest=False):
if self._name2plugin.get(name, None) == -1: if self._name2plugin.get(name, None) == -1:
return return
name = name or getattr(plugin, '__name__', str(id(plugin))) name = name or getattr(plugin, '__name__', str(id(plugin)))
@ -94,20 +152,27 @@ class PluginManager(object):
raise ValueError("Plugin already registered: %s=%s\n%s" %( raise ValueError("Plugin already registered: %s=%s\n%s" %(
name, plugin, self._name2plugin)) name, plugin, self._name2plugin))
#self.trace("registering", name, plugin) #self.trace("registering", name, plugin)
self._name2plugin[name] = plugin
reg = getattr(self, "_registercallback", None) reg = getattr(self, "_registercallback", None)
if reg is not None: if reg is not None:
reg(plugin, name) reg(plugin, name) # may call addhooks
if not prepend: self.hook._scan_plugin(plugin)
self._plugins.append(plugin) self._name2plugin[name] = plugin
if conftest:
self._conftestplugins.append(plugin)
else: else:
self._plugins.insert(0, plugin) if not prepend:
self._plugins.append(plugin)
else:
self._plugins.insert(0, plugin)
return True return True
def unregister(self, plugin=None, name=None): def unregister(self, plugin=None, name=None):
if plugin is None: if plugin is None:
plugin = self.getplugin(name=name) plugin = self.getplugin(name=name)
self._plugins.remove(plugin) try:
self._plugins.remove(plugin)
except KeyError:
self._conftestplugins.remove(plugin)
for name, value in list(self._name2plugin.items()): for name, value in list(self._name2plugin.items()):
if value == plugin: if value == plugin:
del self._name2plugin[name] del self._name2plugin[name]
@ -119,9 +184,8 @@ class PluginManager(object):
while self._shutdown: while self._shutdown:
func = self._shutdown.pop() func = self._shutdown.pop()
func() func()
self._plugins = [] self._plugins = self._conftestplugins = []
self._name2plugin.clear() self._name2plugin.clear()
self._listattrcache.clear()
def isregistered(self, plugin, name=None): def isregistered(self, plugin, name=None):
if self.getplugin(name) is not None: if self.getplugin(name) is not None:
@ -134,7 +198,7 @@ class PluginManager(object):
self.hook._addhooks(spec, prefix=prefix) self.hook._addhooks(spec, prefix=prefix)
def getplugins(self): def getplugins(self):
return list(self._plugins) return self._plugins + self._conftestplugins
def skipifmissing(self, name): def skipifmissing(self, name):
if not self.hasplugin(name): if not self.hasplugin(name):
@ -198,7 +262,8 @@ class PluginManager(object):
self.import_plugin(arg) self.import_plugin(arg)
def consider_conftest(self, conftestmodule): def consider_conftest(self, conftestmodule):
if self.register(conftestmodule, name=conftestmodule.__file__): if self.register(conftestmodule, name=conftestmodule.__file__,
conftest=True):
self.consider_module(conftestmodule) self.consider_module(conftestmodule)
def consider_module(self, mod): def consider_module(self, mod):
@ -233,12 +298,7 @@ class PluginManager(object):
def listattr(self, attrname, plugins=None): def listattr(self, attrname, plugins=None):
if plugins is None: if plugins is None:
plugins = self._plugins plugins = self._plugins + self._conftestplugins
key = (attrname,) + tuple(plugins)
try:
return list(self._listattrcache[key])
except KeyError:
pass
l = [] l = []
last = [] last = []
wrappers = [] wrappers = []
@ -257,7 +317,6 @@ class PluginManager(object):
l.append(meth) l.append(meth)
l.extend(last) l.extend(last)
l.extend(wrappers) l.extend(wrappers)
self._listattrcache[key] = list(l)
return l return l
def call_plugin(self, plugin, methname, kwargs): def call_plugin(self, plugin, methname, kwargs):
@ -288,6 +347,7 @@ class MultiCall:
def __init__(self, methods, kwargs, firstresult=False): def __init__(self, methods, kwargs, firstresult=False):
self.methods = list(methods) self.methods = list(methods)
self.kwargs = kwargs self.kwargs = kwargs
self.kwargs["__multicall__"] = self
self.results = [] self.results = []
self.firstresult = firstresult self.firstresult = firstresult
@ -298,11 +358,12 @@ class MultiCall:
def execute(self): def execute(self):
next_finalizers = [] next_finalizers = []
try: try:
all_kwargs = self.kwargs
while self.methods: while self.methods:
method = self.methods.pop() method = self.methods.pop()
kwargs = self.getkwargs(method) args = [all_kwargs[argname] for argname in varnames(method)]
if hasattr(method, "hookwrapper"): if hasattr(method, "hookwrapper"):
it = method(**kwargs) it = method(*args)
next = getattr(it, "next", None) next = getattr(it, "next", None)
if next is None: if next is None:
next = getattr(it, "__next__", None) next = getattr(it, "__next__", None)
@ -312,7 +373,7 @@ class MultiCall:
res = next() res = next()
next_finalizers.append((method, next)) next_finalizers.append((method, next))
else: else:
res = method(**kwargs) res = method(*args)
if res is not None: if res is not None:
self.results.append(res) self.results.append(res)
if self.firstresult: if self.firstresult:
@ -330,17 +391,7 @@ class MultiCall:
"wrapper contain more than one yield") "wrapper contain more than one yield")
def getkwargs(self, method): def varnames(func, startindex=None):
kwargs = {}
for argname in varnames(method):
try:
kwargs[argname] = self.kwargs[argname]
except KeyError:
if argname == "__multicall__":
kwargs[argname] = self
return kwargs
def varnames(func):
""" return argument name tuple for a function, method, class or callable. """ return argument name tuple for a function, method, class or callable.
In case of a class, its "__init__" method is considered. In case of a class, its "__init__" method is considered.
@ -357,74 +408,130 @@ def varnames(func):
func = func.__init__ func = func.__init__
except AttributeError: except AttributeError:
return () return ()
ismethod = True startindex = 1
else: else:
if not inspect.isfunction(func) and not inspect.ismethod(func): if not inspect.isfunction(func) and not inspect.ismethod(func):
func = getattr(func, '__call__', func) func = getattr(func, '__call__', func)
ismethod = inspect.ismethod(func) if startindex is None:
startindex = int(inspect.ismethod(func))
rawcode = py.code.getrawcode(func) rawcode = py.code.getrawcode(func)
try: try:
x = rawcode.co_varnames[ismethod:rawcode.co_argcount] x = rawcode.co_varnames[startindex:rawcode.co_argcount]
except AttributeError: except AttributeError:
x = () x = ()
else:
defaults = func.__defaults__
if defaults:
x = x[:-len(defaults)]
try: try:
cache["_varnames"] = x cache["_varnames"] = x
except TypeError: except TypeError:
pass pass
return x return x
class HookRelay: class HookRelay:
def __init__(self, hookspecs, pm, prefix="pytest_"): def __init__(self, hookspecs, pm, prefix="pytest_"):
if not isinstance(hookspecs, list): if not isinstance(hookspecs, list):
hookspecs = [hookspecs] hookspecs = [hookspecs]
self._hookspecs = []
self._pm = pm self._pm = pm
self.trace = pm.trace.root.get("hook") self.trace = pm.trace.root.get("hook")
self.prefix = prefix
for hookspec in hookspecs: for hookspec in hookspecs:
self._addhooks(hookspec, prefix) self._addhooks(hookspec, prefix)
def _addhooks(self, hookspecs, prefix): def _addhooks(self, hookspec, prefix):
self._hookspecs.append(hookspecs)
added = False added = False
for name, method in vars(hookspecs).items(): isclass = int(inspect.isclass(hookspec))
for name, method in vars(hookspec).items():
if name.startswith(prefix): if name.startswith(prefix):
firstresult = getattr(method, 'firstresult', False) firstresult = getattr(method, 'firstresult', False)
hc = HookCaller(self, name, firstresult=firstresult) hc = HookCaller(self, name, firstresult=firstresult,
argnames=varnames(method, startindex=isclass))
setattr(self, name, hc) setattr(self, name, hc)
added = True added = True
#print ("setting new hook", name) #print ("setting new hook", name)
if not added: if not added:
raise ValueError("did not find new %r hooks in %r" %( raise ValueError("did not find new %r hooks in %r" %(
prefix, hookspecs,)) prefix, hookspec,))
def _getcaller(self, name, plugins):
caller = getattr(self, name)
methods = self._pm.listattr(name, plugins=plugins)
if methods:
return caller.new_cached_caller(methods)
return caller
def _scan_plugin(self, plugin):
def fail(msg, *args):
name = getattr(plugin, '__name__', plugin)
raise PluginValidationError("plugin %r\n%s" %(name, msg % args))
for name in dir(plugin):
if not name.startswith(self.prefix):
continue
hook = getattr(self, name, None)
method = getattr(plugin, name)
if hook is None:
is_optional = getattr(method, 'optionalhook', False)
if not isgenerichook(name) and not is_optional:
fail("found unknown hook: %r", name)
continue
for arg in varnames(method):
if arg not in hook.argnames:
fail("argument %r not available\n"
"actual definition: %s\n"
"available hookargs: %s",
arg, formatdef(method),
", ".join(hook.argnames))
getattr(self, name).clear_method_cache()
class HookCaller: class HookCaller:
def __init__(self, hookrelay, name, firstresult): def __init__(self, hookrelay, name, firstresult, argnames, methods=None):
self.hookrelay = hookrelay self.hookrelay = hookrelay
self.name = name self.name = name
self.firstresult = firstresult self.firstresult = firstresult
self.trace = self.hookrelay.trace self.methods = methods
self.argnames = ["__multicall__"]
self.argnames.extend(argnames)
assert "self" not in argnames # prevent oversights
def new_cached_caller(self, methods):
return HookCaller(self.hookrelay, self.name, self.firstresult,
argnames=self.argnames, methods=methods)
def __repr__(self): def __repr__(self):
return "<HookCaller %r>" %(self.name,) return "<HookCaller %r>" %(self.name,)
def clear_method_cache(self):
self.methods = None
def __call__(self, **kwargs): def __call__(self, **kwargs):
methods = self.hookrelay._pm.listattr(self.name) methods = self.methods
if methods is None:
self.methods = methods = self.hookrelay._pm.listattr(self.name)
return self._docall(methods, kwargs) return self._docall(methods, kwargs)
def pcall(self, plugins, **kwargs): def callextra(self, methods, **kwargs):
methods = self.hookrelay._pm.listattr(self.name, plugins=plugins) return self._docall(self.methods + methods, kwargs)
return self._docall(methods, kwargs)
def _docall(self, methods, kwargs): def _docall(self, methods, kwargs):
self.trace(self.name, kwargs) return MultiCall(methods, kwargs,
self.trace.root.indent += 1 firstresult=self.firstresult).execute()
mc = MultiCall(methods, kwargs, firstresult=self.firstresult)
try:
res = mc.execute() class PluginValidationError(Exception):
if res: """ plugin failed validation. """
self.trace("finish", self.name, "-->", res)
finally: def isgenerichook(name):
self.trace.root.indent -= 1 return name == "pytest_plugins" or \
return res name.startswith("pytest_funcarg__")
def formatdef(func):
return "%s%s" % (
func.__name__,
inspect.formatargspec(*inspect.getargspec(func))
)

View File

@ -1,8 +1,7 @@
""" version info, help messages, tracing configuration. """ """ version info, help messages, tracing configuration. """
import py import py
import pytest import pytest
import os, inspect, sys import os, sys
from _pytest.core import varnames
def pytest_addoption(parser): def pytest_addoption(parser):
group = parser.getgroup('debugconfig') group = parser.getgroup('debugconfig')
@ -32,7 +31,7 @@ def pytest_cmdline_parse(__multicall__):
f.write("versions pytest-%s, py-%s, python-%s\ncwd=%s\nargs=%s\n\n" %( f.write("versions pytest-%s, py-%s, python-%s\ncwd=%s\nargs=%s\n\n" %(
pytest.__version__, py.__version__, ".".join(map(str, sys.version_info)), pytest.__version__, py.__version__, ".".join(map(str, sys.version_info)),
os.getcwd(), config._origargs)) os.getcwd(), config._origargs))
config.trace.root.setwriter(f.write) config.pluginmanager.set_tracing(f.write)
sys.stderr.write("writing pytestdebug information to %s\n" % path) sys.stderr.write("writing pytestdebug information to %s\n" % path)
return config return config
@ -127,70 +126,3 @@ def pytest_report_header(config):
return lines return lines
# =====================================================
# validate plugin syntax and hooks
# =====================================================
def pytest_plugin_registered(manager, plugin):
methods = collectattr(plugin)
hooks = {}
for hookspec in manager.hook._hookspecs:
hooks.update(collectattr(hookspec))
stringio = py.io.TextIO()
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:
if not getattr(method, 'optionalhook', False):
Print("found unknown hook:", name)
fail = True
else:
#print "checking", method
method_args = list(varnames(method))
if '__multicall__' in method_args:
method_args.remove('__multicall__')
hook = hooks[name]
hookargs = varnames(hook)
for arg in method_args:
if arg not in hookargs:
Print("argument %r not available" %(arg, ))
Print("actual definition: %s" %(formatdef(method)))
Print("available hook arguments: %s" %
", ".join(hookargs))
fail = True
break
#if not fail:
# print "matching hook:", formatdef(method)
if fail:
name = getattr(plugin, '__name__', plugin)
raise PluginValidationError("%s:\n%s" % (name, stringio.getvalue()))
class PluginValidationError(Exception):
""" plugin failed validation. """
def isgenerichook(name):
return name == "pytest_plugins" or \
name.startswith("pytest_funcarg__")
def collectattr(obj):
methods = {}
for apiname in dir(obj):
if apiname.startswith("pytest_"):
methods[apiname] = getattr(obj, apiname)
return methods
def formatdef(func):
return "%s%s" % (
func.__name__,
inspect.formatargspec(*inspect.getargspec(func))
)

View File

@ -142,7 +142,7 @@ def pytest_generate_tests(metafunc):
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# generic runtest related hooks # generic runtest related hooks
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
def pytest_itemstart(item, node=None): def pytest_itemstart(item, node):
""" (deprecated, use pytest_runtest_logstart). """ """ (deprecated, use pytest_runtest_logstart). """
def pytest_runtest_protocol(item, nextitem): def pytest_runtest_protocol(item, nextitem):

View File

@ -153,19 +153,17 @@ def pytest_ignore_collect(path, config):
ignore_paths.extend([py.path.local(x) for x in excludeopt]) ignore_paths.extend([py.path.local(x) for x in excludeopt])
return path in ignore_paths return path in ignore_paths
class HookProxy(object): class FSHookProxy(object):
def __init__(self, fspath, config): def __init__(self, fspath, config):
self.fspath = fspath self.fspath = fspath
self.config = config self.config = config
def __getattr__(self, name): def __getattr__(self, name):
config = object.__getattribute__(self, "config") plugins = self.config._getmatchingplugins(self.fspath)
hookmethod = getattr(config.hook, name) x = self.config.hook._getcaller(name, plugins)
self.__dict__[name] = x
return x
def call_matching_hooks(**kwargs):
plugins = self.config._getmatchingplugins(self.fspath)
return hookmethod.pcall(plugins, **kwargs)
return call_matching_hooks
def compatproperty(name): def compatproperty(name):
def fget(self): def fget(self):
@ -520,6 +518,7 @@ class Session(FSCollector):
self.trace = config.trace.root.get("collection") self.trace = config.trace.root.get("collection")
self._norecursepatterns = config.getini("norecursedirs") self._norecursepatterns = config.getini("norecursedirs")
self.startdir = py.path.local() self.startdir = py.path.local()
self._fs2hookproxy = {}
def pytest_collectstart(self): def pytest_collectstart(self):
if self.shouldstop: if self.shouldstop:
@ -538,7 +537,11 @@ class Session(FSCollector):
return path in self._initialpaths return path in self._initialpaths
def gethookproxy(self, fspath): def gethookproxy(self, fspath):
return HookProxy(fspath, self.config) try:
return self._fs2hookproxy[fspath]
except KeyError:
self._fs2hookproxy[fspath] = x = FSHookProxy(fspath, self.config)
return x
def perform_collect(self, args=None, genitems=True): def perform_collect(self, args=None, genitems=True):
hook = self.config.hook hook = self.config.hook

View File

@ -1,5 +1,4 @@
""" (disabled by default) support for testing pytest and pytest plugins. """ """ (disabled by default) support for testing pytest and pytest plugins. """
import inspect
import sys import sys
import os import os
import codecs import codecs
@ -12,7 +11,7 @@ import subprocess
import py import py
import pytest import pytest
from py.builtin import print_ from py.builtin import print_
from _pytest.core import HookRelay from _pytest.core import HookCaller, add_method_controller
from _pytest.main import Session, EXIT_OK from _pytest.main import Session, EXIT_OK
@ -38,24 +37,10 @@ def pytest_configure(config):
_pytest_fullpath = os.path.abspath(pytest.__file__.rstrip("oc")) _pytest_fullpath = os.path.abspath(pytest.__file__.rstrip("oc"))
_pytest_fullpath = _pytest_fullpath.replace("$py.class", ".py") _pytest_fullpath = _pytest_fullpath.replace("$py.class", ".py")
def pytest_funcarg___pytest(request):
return PytestArg(request)
class PytestArg:
def __init__(self, request):
self.request = request
def gethookrecorder(self, hook):
hookrecorder = HookRecorder(hook._pm)
hookrecorder.start_recording(hook._hookspecs)
self.request.addfinalizer(hookrecorder.finish_recording)
return hookrecorder
class ParsedCall: class ParsedCall:
def __init__(self, name, locals): def __init__(self, name, kwargs):
assert '_name' not in locals self.__dict__.update(kwargs)
self.__dict__.update(locals)
self.__dict__.pop('self')
self._name = name self._name = name
def __repr__(self): def __repr__(self):
@ -63,68 +48,27 @@ class ParsedCall:
del d['_name'] del d['_name']
return "<ParsedCall %r(**%r)>" %(self._name, d) return "<ParsedCall %r(**%r)>" %(self._name, d)
class HookRecorder: class HookRecorder:
def __init__(self, pluginmanager): def __init__(self, pluginmanager):
self._pluginmanager = pluginmanager self._pluginmanager = pluginmanager
self.calls = [] self.calls = []
self._recorders = {}
def start_recording(self, hookspecs): def _docall(hookcaller, methods, kwargs):
if not isinstance(hookspecs, (list, tuple)): self.calls.append(ParsedCall(hookcaller.name, kwargs))
hookspecs = [hookspecs] yield
for hookspec in hookspecs: self._undo_wrapping = add_method_controller(HookCaller, _docall)
assert hookspec not in self._recorders pluginmanager.add_shutdown(self._undo_wrapping)
class RecordCalls:
_recorder = self
for name, method in vars(hookspec).items():
if name[0] != "_":
setattr(RecordCalls, name, self._makecallparser(method))
recorder = RecordCalls()
self._recorders[hookspec] = recorder
self._pluginmanager.register(recorder)
self.hook = HookRelay(hookspecs, pm=self._pluginmanager,
prefix="pytest_")
def finish_recording(self): def finish_recording(self):
for recorder in self._recorders.values(): self._undo_wrapping()
if self._pluginmanager.isregistered(recorder):
self._pluginmanager.unregister(recorder)
self._recorders.clear()
def _makecallparser(self, method):
name = method.__name__
args, varargs, varkw, default = inspect.getargspec(method)
if not args or args[0] != "self":
args.insert(0, 'self')
fspec = inspect.formatargspec(args, varargs, varkw, default)
# we use exec because we want to have early type
# errors on wrong input arguments, using
# *args/**kwargs delays this and gives errors
# elsewhere
exec (py.code.compile("""
def %(name)s%(fspec)s:
self._recorder.calls.append(
ParsedCall(%(name)r, locals()))
""" % locals()))
return locals()[name]
def getcalls(self, names): def getcalls(self, names):
if isinstance(names, str): if isinstance(names, str):
names = names.split() names = names.split()
for name in names: return [call for call in self.calls if call._name in names]
for cls in self._recorders:
if name in vars(cls):
break
else:
raise ValueError("callname %r not found in %r" %(
name, self._recorders.keys()))
l = []
for call in self.calls:
if call._name in names:
l.append(call)
return l
def contains(self, entries): def assert_contains(self, entries):
__tracebackhide__ = True __tracebackhide__ = True
i = 0 i = 0
entries = list(entries) entries = list(entries)
@ -160,6 +104,69 @@ class HookRecorder:
assert len(l) == 1, (name, l) assert len(l) == 1, (name, l)
return l[0] return l[0]
# functionality for test reports
def getreports(self,
names="pytest_runtest_logreport pytest_collectreport"):
return [x.report for x in self.getcalls(names)]
def matchreport(self, inamepart="",
names="pytest_runtest_logreport pytest_collectreport", when=None):
""" return a testreport whose dotted import path matches """
l = []
for rep in self.getreports(names=names):
try:
if not when and rep.when != "call" and rep.passed:
# setup/teardown passing reports - let's ignore those
continue
except AttributeError:
pass
if when and getattr(rep, 'when', None) != when:
continue
if not inamepart or inamepart in rep.nodeid.split("::"):
l.append(rep)
if not l:
raise ValueError("could not find test report matching %r: "
"no test reports at all!" % (inamepart,))
if len(l) > 1:
raise ValueError(
"found 2 or more testreports matching %r: %s" %(inamepart, l))
return l[0]
def getfailures(self,
names='pytest_runtest_logreport pytest_collectreport'):
return [rep for rep in self.getreports(names) if rep.failed]
def getfailedcollections(self):
return self.getfailures('pytest_collectreport')
def listoutcomes(self):
passed = []
skipped = []
failed = []
for rep in self.getreports(
"pytest_collectreport pytest_runtest_logreport"):
if rep.passed:
if getattr(rep, "when", None) == "call":
passed.append(rep)
elif rep.skipped:
skipped.append(rep)
elif rep.failed:
failed.append(rep)
return passed, skipped, failed
def countoutcomes(self):
return [len(x) for x in self.listoutcomes()]
def assertoutcome(self, passed=0, skipped=0, failed=0):
realpassed, realskipped, realfailed = self.listoutcomes()
assert passed == len(realpassed)
assert skipped == len(realskipped)
assert failed == len(realfailed)
def clear(self):
self.calls[:] = []
def pytest_funcarg__linecomp(request): def pytest_funcarg__linecomp(request):
return LineComp() return LineComp()
@ -195,7 +202,6 @@ class TmpTestdir:
def __init__(self, request): def __init__(self, request):
self.request = request self.request = request
self.Config = request.config.__class__ self.Config = request.config.__class__
self._pytest = request.getfuncargvalue("_pytest")
# XXX remove duplication with tmpdir plugin # XXX remove duplication with tmpdir plugin
basetmp = request.config._tmpdirhandler.ensuretemp("testdir") basetmp = request.config._tmpdirhandler.ensuretemp("testdir")
name = request.function.__name__ name = request.function.__name__
@ -226,15 +232,10 @@ class TmpTestdir:
if fn and fn.startswith(str(self.tmpdir)): if fn and fn.startswith(str(self.tmpdir)):
del sys.modules[name] del sys.modules[name]
def getreportrecorder(self, obj): def make_hook_recorder(self, pluginmanager):
if hasattr(obj, 'config'): assert not hasattr(pluginmanager, "reprec")
obj = obj.config pluginmanager.reprec = reprec = HookRecorder(pluginmanager)
if hasattr(obj, 'hook'): self.request.addfinalizer(reprec.finish_recording)
obj = obj.hook
assert hasattr(obj, '_hookspecs'), obj
reprec = ReportRecorder(obj)
reprec.hookrecorder = self._pytest.gethookrecorder(obj)
reprec.hook = reprec.hookrecorder.hook
return reprec return reprec
def chdir(self): def chdir(self):
@ -353,26 +354,23 @@ class TmpTestdir:
def inline_genitems(self, *args): def inline_genitems(self, *args):
return self.inprocess_run(list(args) + ['--collectonly']) return self.inprocess_run(list(args) + ['--collectonly'])
def inline_run(self, *args): def inprocess_run(self, args, plugins=()):
items, rec = self.inprocess_run(args) rec = self.inline_run(*args, plugins=plugins)
return rec items = [x.item for x in rec.getcalls("pytest_itemcollected")]
return items, rec
def inprocess_run(self, args, plugins=None): def inline_run(self, *args, **kwargs):
rec = [] rec = []
items = []
class Collect: class Collect:
def pytest_configure(x, config): def pytest_configure(x, config):
rec.append(self.getreportrecorder(config)) rec.append(self.make_hook_recorder(config.pluginmanager))
def pytest_itemcollected(self, item): plugins = kwargs.get("plugins") or []
items.append(item)
if not plugins:
plugins = []
plugins.append(Collect()) plugins.append(Collect())
ret = pytest.main(list(args), plugins=plugins) ret = pytest.main(list(args), plugins=plugins)
assert len(rec) == 1
reprec = rec[0] reprec = rec[0]
reprec.ret = ret reprec.ret = ret
assert len(rec) == 1 return reprec
return items, reprec
def parseconfig(self, *args): def parseconfig(self, *args):
args = [str(x) for x in args] args = [str(x) for x in args]
@ -547,86 +545,6 @@ def getdecoded(out):
return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % ( return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % (
py.io.saferepr(out),) py.io.saferepr(out),)
class ReportRecorder(object):
def __init__(self, hook):
self.hook = hook
self.pluginmanager = hook._pm
self.pluginmanager.register(self)
def getcall(self, name):
return self.hookrecorder.getcall(name)
def popcall(self, name):
return self.hookrecorder.popcall(name)
def getcalls(self, names):
""" return list of ParsedCall instances matching the given eventname. """
return self.hookrecorder.getcalls(names)
# functionality for test reports
def getreports(self, names="pytest_runtest_logreport pytest_collectreport"):
return [x.report for x in self.getcalls(names)]
def matchreport(self, inamepart="",
names="pytest_runtest_logreport pytest_collectreport", when=None):
""" return a testreport whose dotted import path matches """
l = []
for rep in self.getreports(names=names):
try:
if not when and rep.when != "call" and rep.passed:
# setup/teardown passing reports - let's ignore those
continue
except AttributeError:
pass
if when and getattr(rep, 'when', None) != when:
continue
if not inamepart or inamepart in rep.nodeid.split("::"):
l.append(rep)
if not l:
raise ValueError("could not find test report matching %r: no test reports at all!" %
(inamepart,))
if len(l) > 1:
raise ValueError("found more than one testreport matching %r: %s" %(
inamepart, l))
return l[0]
def getfailures(self, names='pytest_runtest_logreport pytest_collectreport'):
return [rep for rep in self.getreports(names) if rep.failed]
def getfailedcollections(self):
return self.getfailures('pytest_collectreport')
def listoutcomes(self):
passed = []
skipped = []
failed = []
for rep in self.getreports(
"pytest_collectreport pytest_runtest_logreport"):
if rep.passed:
if getattr(rep, "when", None) == "call":
passed.append(rep)
elif rep.skipped:
skipped.append(rep)
elif rep.failed:
failed.append(rep)
return passed, skipped, failed
def countoutcomes(self):
return [len(x) for x in self.listoutcomes()]
def assertoutcome(self, passed=0, skipped=0, failed=0):
realpassed, realskipped, realfailed = self.listoutcomes()
assert passed == len(realpassed)
assert skipped == len(realskipped)
assert failed == len(realfailed)
def clear(self):
self.hookrecorder.calls[:] = []
def unregister(self):
self.pluginmanager.unregister(self)
self.hookrecorder.finish_recording()
class LineComp: class LineComp:
def __init__(self): def __init__(self):

View File

@ -353,15 +353,17 @@ class PyCollector(PyobjMixin, pytest.Collector):
fixtureinfo = fm.getfixtureinfo(self, funcobj, cls) fixtureinfo = fm.getfixtureinfo(self, funcobj, cls)
metafunc = Metafunc(funcobj, fixtureinfo, self.config, metafunc = Metafunc(funcobj, fixtureinfo, self.config,
cls=cls, module=module) cls=cls, module=module)
gentesthook = self.config.hook.pytest_generate_tests try:
extra = [module] methods = [module.pytest_generate_tests]
if cls is not None: except AttributeError:
extra.append(cls()) methods = []
plugins = self.getplugins() + extra if hasattr(cls, "pytest_generate_tests"):
gentesthook.pcall(plugins, metafunc=metafunc) methods.append(cls().pytest_generate_tests)
self.ihook.pytest_generate_tests.callextra(methods, metafunc=metafunc)
Function = self._getcustomclass("Function") Function = self._getcustomclass("Function")
if not metafunc._calls: if not metafunc._calls:
yield Function(name, parent=self) yield Function(name, parent=self, fixtureinfo=fixtureinfo)
else: else:
# add funcargs() as fixturedefs to fixtureinfo.arg2fixturedefs # add funcargs() as fixturedefs to fixtureinfo.arg2fixturedefs
add_funcarg_pseudo_fixture_def(self, metafunc, fm) add_funcarg_pseudo_fixture_def(self, metafunc, fm)
@ -370,6 +372,7 @@ class PyCollector(PyobjMixin, pytest.Collector):
subname = "%s[%s]" %(name, callspec.id) subname = "%s[%s]" %(name, callspec.id)
yield Function(name=subname, parent=self, yield Function(name=subname, parent=self,
callspec=callspec, callobj=funcobj, callspec=callspec, callobj=funcobj,
fixtureinfo=fixtureinfo,
keywords={callspec.id:True}) keywords={callspec.id:True})
def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager): def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager):
@ -1065,28 +1068,27 @@ class Function(FunctionMixin, pytest.Item, FuncargnamesCompatAttr):
""" """
_genid = None _genid = None
def __init__(self, name, parent, args=None, config=None, def __init__(self, name, parent, args=None, config=None,
callspec=None, callobj=NOTSET, keywords=None, session=None): callspec=None, callobj=NOTSET, keywords=None, session=None,
fixtureinfo=None):
super(Function, self).__init__(name, parent, config=config, super(Function, self).__init__(name, parent, config=config,
session=session) session=session)
self._args = args self._args = args
if callobj is not NOTSET: if callobj is not NOTSET:
self.obj = callobj self.obj = callobj
for name, val in (py.builtin._getfuncdict(self.obj) or {}).items(): self.keywords.update(self.obj.__dict__)
self.keywords[name] = val
if callspec: if callspec:
for name, val in callspec.keywords.items():
self.keywords[name] = val
if keywords:
for name, val in keywords.items():
self.keywords[name] = val
isyield = self._isyieldedfunction()
self._fixtureinfo = fi = self.session._fixturemanager.getfixtureinfo(
self.parent, self.obj, self.cls, funcargs=not isyield)
self.fixturenames = fi.names_closure
if callspec is not None:
self.callspec = callspec self.callspec = callspec
self.keywords.update(callspec.keywords)
if keywords:
self.keywords.update(keywords)
if fixtureinfo is None:
fixtureinfo = self.session._fixturemanager.getfixtureinfo(
self.parent, self.obj, self.cls,
funcargs=not self._isyieldedfunction())
self._fixtureinfo = fixtureinfo
self.fixturenames = fixtureinfo.names_closure
self._initrequest() self._initrequest()
def _initrequest(self): def _initrequest(self):
@ -1571,15 +1573,8 @@ class FixtureManager:
self._nodeid_and_autousenames = [("", self.config.getini("usefixtures"))] self._nodeid_and_autousenames = [("", self.config.getini("usefixtures"))]
session.config.pluginmanager.register(self, "funcmanage") session.config.pluginmanager.register(self, "funcmanage")
self._nodename2fixtureinfo = {}
def getfixtureinfo(self, node, func, cls, funcargs=True): def getfixtureinfo(self, node, func, cls, funcargs=True):
# node is the "collection node" for "func"
key = (node, func)
try:
return self._nodename2fixtureinfo[key]
except KeyError:
pass
if funcargs and not hasattr(node, "nofuncargs"): if funcargs and not hasattr(node, "nofuncargs"):
if cls is not None: if cls is not None:
startindex = 1 startindex = 1
@ -1595,10 +1590,7 @@ class FixtureManager:
fm = node.session._fixturemanager fm = node.session._fixturemanager
names_closure, arg2fixturedefs = fm.getfixtureclosure(initialnames, names_closure, arg2fixturedefs = fm.getfixtureclosure(initialnames,
node) node)
fixtureinfo = FuncFixtureInfo(argnames, names_closure, return FuncFixtureInfo(argnames, names_closure, arg2fixturedefs)
arg2fixturedefs)
self._nodename2fixtureinfo[key] = fixtureinfo
return fixtureinfo
### XXX this hook should be called for historic events like pytest_configure ### XXX this hook should be called for historic events like pytest_configure
### so that we don't have to do the below pytest_configure hook ### so that we don't have to do the below pytest_configure hook

View File

@ -9,4 +9,4 @@ if __name__ == '__main__':
p = pstats.Stats("prof") p = pstats.Stats("prof")
p.strip_dirs() p.strip_dirs()
p.sort_stats('cumulative') p.sort_stats('cumulative')
print(p.print_stats(250)) print(p.print_stats(500))

View File

@ -334,9 +334,9 @@ class TestSession:
assert item.name == "test_func" assert item.name == "test_func"
newid = item.nodeid newid = item.nodeid
assert newid == id assert newid == id
py.std.pprint.pprint(hookrec.hookrecorder.calls) py.std.pprint.pprint(hookrec.calls)
topdir = testdir.tmpdir # noqa topdir = testdir.tmpdir # noqa
hookrec.hookrecorder.contains([ hookrec.assert_contains([
("pytest_collectstart", "collector.fspath == topdir"), ("pytest_collectstart", "collector.fspath == topdir"),
("pytest_make_collect_report", "collector.fspath == topdir"), ("pytest_make_collect_report", "collector.fspath == topdir"),
("pytest_collectstart", "collector.fspath == p"), ("pytest_collectstart", "collector.fspath == p"),
@ -381,9 +381,9 @@ class TestSession:
id = p.basename id = p.basename
items, hookrec = testdir.inline_genitems(id) items, hookrec = testdir.inline_genitems(id)
py.std.pprint.pprint(hookrec.hookrecorder.calls) py.std.pprint.pprint(hookrec.calls)
assert len(items) == 2 assert len(items) == 2
hookrec.hookrecorder.contains([ hookrec.assert_contains([
("pytest_collectstart", ("pytest_collectstart",
"collector.fspath == collector.session.fspath"), "collector.fspath == collector.session.fspath"),
("pytest_collectstart", ("pytest_collectstart",
@ -404,8 +404,8 @@ class TestSession:
items, hookrec = testdir.inline_genitems() items, hookrec = testdir.inline_genitems()
assert len(items) == 1 assert len(items) == 1
py.std.pprint.pprint(hookrec.hookrecorder.calls) py.std.pprint.pprint(hookrec.calls)
hookrec.hookrecorder.contains([ hookrec.assert_contains([
("pytest_collectstart", "collector.fspath == test_aaa"), ("pytest_collectstart", "collector.fspath == test_aaa"),
("pytest_pycollect_makeitem", "name == 'test_func'"), ("pytest_pycollect_makeitem", "name == 'test_func'"),
("pytest_collectreport", ("pytest_collectreport",
@ -425,8 +425,8 @@ class TestSession:
items, hookrec = testdir.inline_genitems(id) items, hookrec = testdir.inline_genitems(id)
assert len(items) == 2 assert len(items) == 2
py.std.pprint.pprint(hookrec.hookrecorder.calls) py.std.pprint.pprint(hookrec.calls)
hookrec.hookrecorder.contains([ hookrec.assert_contains([
("pytest_collectstart", "collector.fspath == test_aaa"), ("pytest_collectstart", "collector.fspath == test_aaa"),
("pytest_pycollect_makeitem", "name == 'test_func'"), ("pytest_pycollect_makeitem", "name == 'test_func'"),
("pytest_collectreport", "report.nodeid == 'aaa/test_aaa.py'"), ("pytest_collectreport", "report.nodeid == 'aaa/test_aaa.py'"),

View File

@ -149,7 +149,7 @@ class TestBootstrapping:
mod.pytest_plugins = "pytest_a" mod.pytest_plugins = "pytest_a"
aplugin = testdir.makepyfile(pytest_a="#") aplugin = testdir.makepyfile(pytest_a="#")
pluginmanager = get_plugin_manager() pluginmanager = get_plugin_manager()
reprec = testdir.getreportrecorder(pluginmanager) reprec = testdir.make_hook_recorder(pluginmanager)
#syspath.prepend(aplugin.dirpath()) #syspath.prepend(aplugin.dirpath())
py.std.sys.path.insert(0, str(aplugin.dirpath())) py.std.sys.path.insert(0, str(aplugin.dirpath()))
pluginmanager.consider_module(mod) pluginmanager.consider_module(mod)
@ -274,7 +274,7 @@ class TestBootstrapping:
saveindent.append(pm.trace.root.indent) saveindent.append(pm.trace.root.indent)
raise ValueError(42) raise ValueError(42)
l = [] l = []
pm.trace.root.setwriter(l.append) pm.set_tracing(l.append)
indent = pm.trace.root.indent indent = pm.trace.root.indent
p = api1() p = api1()
pm.register(p) pm.register(p)
@ -405,11 +405,7 @@ class TestPytestPluginInteractions:
pluginmanager.register(p3) pluginmanager.register(p3)
methods = pluginmanager.listattr('m') methods = pluginmanager.listattr('m')
assert methods == [p2.m, p3.m, p1.m] assert methods == [p2.m, p3.m, p1.m]
# listattr keeps a cache and deleting
# a function attribute requires clearing it
pluginmanager._listattrcache.clear()
del P1.m.__dict__['tryfirst'] del P1.m.__dict__['tryfirst']
pytest.mark.trylast(getattr(P2.m, 'im_func', P2.m)) pytest.mark.trylast(getattr(P2.m, 'im_func', P2.m))
methods = pluginmanager.listattr('m') methods = pluginmanager.listattr('m')
assert methods == [p2.m, p1.m, p3.m] assert methods == [p2.m, p1.m, p3.m]
@ -436,6 +432,11 @@ def test_varnames():
assert varnames(A().f) == ('y',) assert varnames(A().f) == ('y',)
assert varnames(B()) == ('z',) assert varnames(B()) == ('z',)
def test_varnames_default():
def f(x, y=3):
pass
assert varnames(f) == ("x",)
def test_varnames_class(): def test_varnames_class():
class C: class C:
def __init__(self, x): def __init__(self, x):
@ -494,12 +495,10 @@ class TestMultiCall:
return x + z return x + z
reslist = MultiCall([f], dict(x=23, y=24)).execute() reslist = MultiCall([f], dict(x=23, y=24)).execute()
assert reslist == [24] assert reslist == [24]
reslist = MultiCall([f], dict(x=23, z=2)).execute()
assert reslist == [25]
def test_tags_call_error(self): def test_tags_call_error(self):
multicall = MultiCall([lambda x: x], {}) multicall = MultiCall([lambda x: x], {})
pytest.raises(TypeError, multicall.execute) pytest.raises(KeyError, multicall.execute)
def test_call_subexecute(self): def test_call_subexecute(self):
def m(__multicall__): def m(__multicall__):
@ -630,6 +629,18 @@ class TestHookRelay:
assert l == [4] assert l == [4]
assert not hasattr(mcm, 'world') assert not hasattr(mcm, 'world')
def test_argmismatch(self):
class Api:
def hello(self, arg):
"api hook 1"
pm = PluginManager(Api, prefix="he")
class Plugin:
def hello(self, argwrong):
return arg + 1
with pytest.raises(PluginValidationError) as exc:
pm.register(Plugin())
assert "argwrong" in str(exc.value)
def test_only_kwargs(self): def test_only_kwargs(self):
pm = PluginManager() pm = PluginManager()
class Api: class Api:
@ -754,3 +765,96 @@ def test_importplugin_issue375(testdir):
assert "qwe" not in str(excinfo.value) assert "qwe" not in str(excinfo.value)
assert "aaaa" in str(excinfo.value) assert "aaaa" in str(excinfo.value)
class TestWrapMethod:
def test_basic_happypath(self):
class A:
def f(self):
return "A.f"
l = []
def f(self):
l.append(1)
yield
l.append(2)
undo = add_method_controller(A, f)
assert A().f() == "A.f"
assert l == [1,2]
undo()
l[:] = []
assert A().f() == "A.f"
assert l == []
def test_method_raises(self):
class A:
def error(self, val):
raise ValueError(val)
l = []
def error(self, val):
l.append(val)
try:
yield
except ValueError:
l.append(None)
raise
undo = add_method_controller(A, error)
with pytest.raises(ValueError):
A().error(42)
assert l == [42, None]
undo()
l[:] = []
with pytest.raises(ValueError):
A().error(42)
assert l == []
def test_controller_swallows_method_raises(self):
class A:
def error(self, val):
raise ValueError(val)
def error(self, val):
try:
yield
except ValueError:
yield 2
add_method_controller(A, error)
assert A().error(42) == 2
def test_reraise_on_controller_StopIteration(self):
class A:
def error(self, val):
raise ValueError(val)
def error(self, val):
try:
yield
except ValueError:
pass
add_method_controller(A, error)
with pytest.raises(ValueError):
A().error(42)
@pytest.mark.xfail(reason="if needed later")
def test_modify_call_args(self):
class A:
def error(self, val1, val2):
raise ValueError(val1+val2)
l = []
def error(self):
try:
yield (1,), {'val2': 2}
except ValueError as ex:
assert ex.args == (3,)
l.append(1)
add_method_controller(A, error)
with pytest.raises(ValueError):
A().error()
assert l == [1]

View File

@ -1,5 +1,4 @@
import py, pytest import pytest
from _pytest.helpconfig import collectattr
def test_version(testdir, pytestconfig): def test_version(testdir, pytestconfig):
result = testdir.runpytest("--version") result = testdir.runpytest("--version")
@ -25,18 +24,6 @@ def test_help(testdir):
*to see*fixtures*py.test --fixtures* *to see*fixtures*py.test --fixtures*
""") """)
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']
def test_hookvalidation_unknown(testdir): def test_hookvalidation_unknown(testdir):
testdir.makeconftest(""" testdir.makeconftest("""
def pytest_hello(xyz): def pytest_hello(xyz):

View File

@ -3,9 +3,9 @@ import os
from _pytest.pytester import HookRecorder from _pytest.pytester import HookRecorder
from _pytest.core import PluginManager from _pytest.core import PluginManager
def test_reportrecorder(testdir): def test_make_hook_recorder(testdir):
item = testdir.getitem("def test_func(): pass") item = testdir.getitem("def test_func(): pass")
recorder = testdir.getreportrecorder(item.config) recorder = testdir.make_hook_recorder(item.config.pluginmanager)
assert not recorder.getfailures() assert not recorder.getfailures()
pytest.xfail("internal reportrecorder tests need refactoring") pytest.xfail("internal reportrecorder tests need refactoring")
@ -71,47 +71,37 @@ def test_testdir_runs_with_plugin(testdir):
"*1 passed*" "*1 passed*"
]) ])
def test_hookrecorder_basic():
rec = HookRecorder(PluginManager()) def make_holder():
class ApiClass: class apiclass:
def pytest_xyz(self, arg): def pytest_xyz(self, arg):
"x" "x"
rec.start_recording(ApiClass) def pytest_xyz_noarg(self):
rec.hook.pytest_xyz(arg=123) "x"
apimod = type(os)('api')
def pytest_xyz(arg):
"x"
def pytest_xyz_noarg():
"x"
apimod.pytest_xyz = pytest_xyz
apimod.pytest_xyz_noarg = pytest_xyz_noarg
return apiclass, apimod
@pytest.mark.parametrize("holder", make_holder())
def test_hookrecorder_basic(holder):
pm = PluginManager()
pm.hook._addhooks(holder, "pytest_")
rec = HookRecorder(pm)
pm.hook.pytest_xyz(arg=123)
call = rec.popcall("pytest_xyz") call = rec.popcall("pytest_xyz")
assert call.arg == 123 assert call.arg == 123
assert call._name == "pytest_xyz" assert call._name == "pytest_xyz"
pytest.raises(pytest.fail.Exception, "rec.popcall('abc')") pytest.raises(pytest.fail.Exception, "rec.popcall('abc')")
pm.hook.pytest_xyz_noarg()
def test_hookrecorder_basic_no_args_hook(): call = rec.popcall("pytest_xyz_noarg")
rec = HookRecorder(PluginManager()) assert call._name == "pytest_xyz_noarg"
apimod = type(os)('api')
def pytest_xyz():
"x"
apimod.pytest_xyz = pytest_xyz
rec.start_recording(apimod)
rec.hook.pytest_xyz()
call = rec.popcall("pytest_xyz")
assert call._name == "pytest_xyz"
def test_functional(testdir, linecomp):
reprec = testdir.inline_runsource("""
import pytest
from _pytest.core import HookRelay, PluginManager
pytest_plugins="pytester"
def test_func(_pytest):
class ApiClass:
def pytest_xyz(self, arg): "x"
hook = HookRelay([ApiClass], PluginManager())
rec = _pytest.gethookrecorder(hook)
class Plugin:
def pytest_xyz(self, arg):
return arg + 1
rec._pluginmanager.register(Plugin())
res = rec.hook.pytest_xyz(arg=41)
assert res == [42]
""")
reprec.assertoutcome(passed=1)
def test_makepyfile_unicode(testdir): def test_makepyfile_unicode(testdir):