re-introduce the old 2.2.4 FuncargRequest implementation as it is a better

base for implementing the new funcarg/setup api. Also Un-optimize
funcargnames discovery for now.
This commit is contained in:
holger krekel 2012-07-18 19:49:14 +02:00
parent 4766497515
commit c7ee6e71ab
8 changed files with 365 additions and 210 deletions

View File

@ -187,7 +187,7 @@ def pytest_funcarg__capsys(request):
captured output available via ``capsys.readouterr()`` method calls captured output available via ``capsys.readouterr()`` method calls
which return a ``(out, err)`` tuple. which return a ``(out, err)`` tuple.
""" """
if "capfd" in request.funcargs: if "capfd" in request._funcargs:
raise request.LookupError(error_capsysfderror) raise request.LookupError(error_capsysfderror)
return CaptureFuncarg(py.io.StdCapture) return CaptureFuncarg(py.io.StdCapture)
@ -196,7 +196,7 @@ def pytest_funcarg__capfd(request):
captured output available via ``capsys.readouterr()`` method calls captured output available via ``capsys.readouterr()`` method calls
which return a ``(out, err)`` tuple. which return a ``(out, err)`` tuple.
""" """
if "capsys" in request.funcargs: if "capsys" in request._funcargs:
raise request.LookupError(error_capsysfderror) raise request.LookupError(error_capsysfderror)
if not hasattr(os, 'dup'): if not hasattr(os, 'dup'):
pytest.skip("capfd funcarg needs os.dup") pytest.skip("capfd funcarg needs os.dup")

97
_pytest/impl Normal file
View File

@ -0,0 +1,97 @@
Implementation plan for resources
------------------------------------------
1. Revert FuncargRequest to the old form, unmerge item/request
2. make setup functions be discovered at collection time
3. make funcarg factories be discovered at collection time
4. Introduce funcarg marker
5. Introduce funcarg scope parameter
6. Introduce funcarg parametrize parameter
7. (Introduce a pytest_fixture_protocol/setup_funcargs hook)
methods and data structures
--------------------------------
A FuncarcDB holds all information about funcarg definitions,
parametrization and the places where funcargs are required. It can
answer the following questions:
* given a node and a funcargname, return a paramlist so that collection
can perform parametrization (parametrized nodes?)
* given a node (possibly containing a param), perform a funcargrequest
and return the value
* if funcargname is an empty string, it matches general setup.
pytest could perform 2-pass collection:
- first perform normal collection (no parametrization at all!), populate
FuncargDB
- walk through the node tree and ask FuncargDB for each node for
required funcargs and their parameters - clone subtrees (deepcopy) and
substitute the un-parametrized node with parametrized ones
as a simple example, let's consider a tree where a test function requires
a "abc" funcarg and its factory defines it as parametrized and scoped
for Modules. When the 2nd collection pass asks FuncargDB to return
params for the test module, it will know that the test functions in it
requires "abc" and that is it parametrized and defined for module scope.
Therefore parametrization of the module node is performed, substituting
the node with multiple module nodes ("test_module.py[1]", ...).
When test_module.py[1] is setup() it will call all its (parametrized)
factories and populate a funcargs dictionary, mapping funcargnames to values.
When a test function below test_module.py[1] is executed, it looks up
its required arguments from the thus populated funcargs dictionary.
Let's add to this example a second funcarg "def" that has a per-function parametrization. When the 2nd collection pass asks FuncargDB to return
params for the test function, it will know that the test functions in it
requires "def" and that is it parametrized and defined for function scope.
Therefore parametrization of the function node is performed, substituting
the node with multiple function nodes ("test_function[1]", ...).
When test_function[1] is setup() it will call all its (parametrized)
factories and populate a funcargs dictionary. The "def" will only appear
in the funcargs dict seen by test_function[1]. When test_function[1]
executes, it will use its funcargs.
where
* ``nodeidbase`` is a basestring; for all nodeids matching
startswith(nodeidbase) it defines a (scopecls, factorylist) tuple
* ``scopecls`` is a node class for the which the factorylist s defined
* ``param`` is a parametrizing parameter for the factorylist
* ``factorylist`` is a list of factories which will be used to perform
a funcarg request
* the whole list is sorted by length of nodeidbase (longest first)
conftest loading:
each funcarg-factory will populate FuncargDefs which keeps references
to all definitions the funcarg2 marked function or pytest_funcarg__
scope can be a string or a nodenames-tuple.
scopestring -> list of (funcargname, factorylist)
nodenames -> (funcargname, list of factories)
It needs to be a list because factories can decorate
For any given node and a required funcarg it is thus
easy to lookup a list of matching factories.
When a test item is collected, it grows a dictionary
(funcargname2factorycalllist). A factory lookup is performed
for each required funcarg. The resulting factory call is stored
with the item. If a function is parametrized multiple items are
created with respective factory calls. Else if a factory is parametrized
multiple items and calls to the factory function are created as well.
At setup time, an item populates a funcargs mapping, mapping names
to values. If a value is funcarg factories are queried for a given item
test functions and setup functions are put in a class
which looks up required funcarg factories.

View File

@ -177,11 +177,11 @@ class Node(object):
#: fspath sensitive hook proxy used to call pytest hooks #: fspath sensitive hook proxy used to call pytest hooks
self.ihook = self.session.gethookproxy(self.fspath) self.ihook = self.session.gethookproxy(self.fspath)
self.extrainit() #self.extrainit()
def extrainit(self): #def extrainit(self):
""""extra initialization after Node is initialized. Implemented # """"extra initialization after Node is initialized. Implemented
by some subclasses. """ # by some subclasses. """
Module = compatproperty("Module") Module = compatproperty("Module")
Class = compatproperty("Class") Class = compatproperty("Class")

View File

@ -33,126 +33,6 @@ def pyobj_property(name):
name.lower(),) name.lower(),)
return property(get, None, None, doc) return property(get, None, None, doc)
class Request(object):
_argprefix = "pytest_funcarg__"
class LookupError(LookupError):
""" error while performing funcarg factory lookup. """
def extrainit(self):
self._name2factory = {}
self._currentarg = None
self.funcargs = None # later set to a dict from fillfuncargs() or
# from getfuncargvalue(). Setting it to
# None prevents users from performing
# "name in item.funcargs" checks too early.
@property
def _plugins(self):
extra = [obj for obj in (self.module, self.instance) if obj]
return self.getplugins() + extra
def _getscopeitem(self, scope):
if scope == "function":
return self
elif scope == "session":
return None
elif scope == "class":
x = self.getparent(pytest.Class)
if x is not None:
return x
scope = "module"
if scope == "module":
return self.getparent(pytest.Module)
raise ValueError("unknown scope %r" %(scope,))
def getfuncargvalue(self, argname):
""" Retrieve a named function argument value.
This function looks up a matching factory and invokes
it to obtain the return value. The factory receives
can itself perform recursive calls to this method,
either for using multiple other funcarg values under the hood
or to decorate values from other factories matching the same name.
"""
try:
return self.funcargs[argname]
except KeyError:
pass
except TypeError:
self.funcargs = getattr(self, "_funcargs", {})
if argname not in self._name2factory:
self._name2factory[argname] = self.config.pluginmanager.listattr(
plugins=self._plugins,
attrname=self._argprefix + str(argname)
)
#else: we are called recursively
if not self._name2factory[argname]:
self._raiselookupfailed(argname)
funcargfactory = self._name2factory[argname].pop()
oldarg = self._currentarg
mp = monkeypatch()
mp.setattr(self, '_currentarg', argname)
try:
param = self.callspec.getparam(argname)
except (AttributeError, ValueError):
pass
else:
mp.setattr(self, 'param', param, raising=False)
try:
self.funcargs[argname] = res = funcargfactory(self)
finally:
mp.undo()
return res
def addfinalizer(self, finalizer):
""" add a no-args finalizer function to be called when the underlying
node is torn down."""
self.session._setupstate.addfinalizer(finalizer, self)
def cached_setup(self, setup, teardown=None,
scope="module", extrakey=None):
""" Return a cached testing resource created by ``setup`` &
detroyed by a respective ``teardown(resource)`` call.
:arg teardown: function receiving a previously setup resource.
:arg setup: a no-argument function creating a resource.
:arg scope: a string value out of ``function``, ``class``, ``module``
or ``session`` indicating the caching lifecycle of the resource.
:arg extrakey: added to internal caching key.
"""
if not hasattr(self.config, '_setupcache'):
self.config._setupcache = {} # XXX weakref?
colitem = self._getscopeitem(scope)
cachekey = (self._currentarg, colitem, extrakey)
cache = self.config._setupcache
try:
val = cache[cachekey]
except KeyError:
val = setup()
cache[cachekey] = val
if teardown is not None:
def finalizer():
del cache[cachekey]
teardown(val)
self.session._setupstate.addfinalizer(finalizer, colitem)
return val
def _raiselookupfailed(self, argname):
available = []
for plugin in self._plugins:
for name in vars(plugin):
if name.startswith(self._argprefix):
name = name[len(self._argprefix):]
if name not in available:
available.append(name)
fspath, lineno, msg = self.reportinfo()
msg = "LookupError: no factory found for function argument %r" % (argname,)
msg += "\n available funcargs: %s" %(", ".join(available),)
msg += "\n use 'py.test --funcargs [testpath]' for help on them."
raise self.LookupError(msg)
def pytest_addoption(parser): def pytest_addoption(parser):
group = parser.getgroup("general") group = parser.getgroup("general")
@ -222,6 +102,15 @@ def pytest_pyfunc_call(__multicall__, pyfuncitem):
funcargs[name] = pyfuncitem.funcargs[name] funcargs[name] = pyfuncitem.funcargs[name]
testfunction(**funcargs) testfunction(**funcargs)
def pytest_pyfunc_call(__multicall__, pyfuncitem):
if not __multicall__.execute():
testfunction = pyfuncitem.obj
if pyfuncitem._isyieldedfunction():
testfunction(*pyfuncitem._args)
else:
funcargs = pyfuncitem.funcargs
testfunction(**funcargs)
def pytest_collect_file(path, parent): def pytest_collect_file(path, parent):
ext = path.ext ext = path.ext
pb = path.purebasename pb = path.purebasename
@ -267,7 +156,7 @@ class PyobjContext(object):
cls = pyobj_property("Class") cls = pyobj_property("Class")
instance = pyobj_property("Instance") instance = pyobj_property("Instance")
class PyobjMixin(Request, PyobjContext): class PyobjMixin(PyobjContext):
def obj(): def obj():
def fget(self): def fget(self):
try: try:
@ -390,14 +279,12 @@ class PyCollector(PyobjMixin, pytest.Collector):
gentesthook.pcall(plugins, metafunc=metafunc) gentesthook.pcall(plugins, metafunc=metafunc)
Function = self._getcustomclass("Function") Function = self._getcustomclass("Function")
if not metafunc._calls: if not metafunc._calls:
return Function(name, parent=self, return Function(name, parent=self)
funcargnames=metafunc.funcargnames)
l = [] l = []
for callspec in metafunc._calls: for callspec in metafunc._calls:
subname = "%s[%s]" %(name, callspec.id) subname = "%s[%s]" %(name, callspec.id)
function = Function(name=subname, parent=self, function = Function(name=subname, parent=self,
callspec=callspec, callobj=funcobj, callspec=callspec, callobj=funcobj,
funcargnames=metafunc.funcargnames,
keywords={callspec.id:True}) keywords={callspec.id:True})
l.append(function) l.append(function)
return l return l
@ -494,6 +381,7 @@ class Instance(PyCollector):
class FunctionMixin(PyobjMixin): class FunctionMixin(PyobjMixin):
""" mixin for the code common to Function and Generator. """ mixin for the code common to Function and Generator.
""" """
def setup(self): def setup(self):
""" perform setup for this test function. """ """ perform setup for this test function. """
if hasattr(self, '_preservedparent'): if hasattr(self, '_preservedparent'):
@ -535,7 +423,7 @@ class FunctionMixin(PyobjMixin):
excinfo.traceback = ntraceback.filter() excinfo.traceback = ntraceback.filter()
def _repr_failure_py(self, excinfo, style="long"): def _repr_failure_py(self, excinfo, style="long"):
if excinfo.errisinstance(Request.LookupError): if excinfo.errisinstance(FuncargRequest.LookupError):
fspath, lineno, msg = self.reportinfo() fspath, lineno, msg = self.reportinfo()
lines, _ = inspect.getsourcelines(self.obj) lines, _ = inspect.getsourcelines(self.obj)
for i, line in enumerate(lines): for i, line in enumerate(lines):
@ -626,10 +514,21 @@ def getfuncargnames(function, startindex=None):
return argnames[startindex:-numdefaults] return argnames[startindex:-numdefaults]
return argnames[startindex:] return argnames[startindex:]
def fillfuncargs(node): def fillfuncargs(function):
""" fill missing funcargs. """ """ fill missing funcargs. """
if not isinstance(node, Function): #if not getattr(function, "_args", None) is not None:
node = OldFuncargRequest(pyfuncitem=node) # request = FuncargRequest(pyfuncitem=function)
# request._fillfuncargs()
if getattr(function, "_args", None) is None:
try:
request = function._request
except AttributeError:
request = FuncargRequest(function)
request._fillfuncargs()
def XXXfillfuncargs(node):
""" fill missing funcargs. """
node = FuncargRequest(node)
if node.funcargs is None: if node.funcargs is None:
node.funcargs = getattr(node, "_funcargs", {}) node.funcargs = getattr(node, "_funcargs", {})
if not isinstance(node, Function) or not node._isyieldedfunction(): if not isinstance(node, Function) or not node._isyieldedfunction():
@ -815,8 +714,8 @@ def _showfuncargs_main(config, session):
for plugin in plugins: for plugin in plugins:
available = [] available = []
for name, factory in vars(plugin).items(): for name, factory in vars(plugin).items():
if name.startswith(Request._argprefix): if name.startswith(FuncargRequest._argprefix):
name = name[len(Request._argprefix):] name = name[len(FuncargRequest._argprefix):]
if name not in available: if name not in available:
available.append([name, factory]) available.append([name, factory])
if available: if available:
@ -931,11 +830,9 @@ class Function(FunctionMixin, pytest.Item):
""" """
_genid = None _genid = None
def __init__(self, name, parent=None, args=None, config=None, def __init__(self, name, parent=None, args=None, config=None,
callspec=None, callobj=_dummy, keywords=None, callspec=None, callobj=_dummy, keywords=None, session=None):
session=None, funcargnames=()):
super(Function, self).__init__(name, parent, config=config, super(Function, self).__init__(name, parent, config=config,
session=session) session=session)
self.funcargnames = funcargnames
self._args = args self._args = args
if self._isyieldedfunction(): if self._isyieldedfunction():
assert not callspec, ( assert not callspec, (
@ -943,12 +840,17 @@ class Function(FunctionMixin, pytest.Item):
else: else:
if callspec is not None: if callspec is not None:
self.callspec = callspec self.callspec = callspec
self._funcargs = callspec.funcargs or {} self.funcargs = callspec.funcargs or {}
self._genid = callspec.id self._genid = callspec.id
if hasattr(callspec, "param"): if hasattr(callspec, "param"):
self.param = callspec.param self.param = callspec.param
else:
self.funcargs = {}
self._request = req = FuncargRequest(self)
if callobj is not _dummy: if callobj is not _dummy:
self._obj = callobj self.obj = callobj
startindex = int(self.cls is not None)
self.funcargnames = getfuncargnames(self.obj, startindex=startindex)
self.keywords.update(py.builtin._getfuncdict(self.obj) or {}) self.keywords.update(py.builtin._getfuncdict(self.obj) or {})
if keywords: if keywords:
@ -1002,49 +904,196 @@ class Function(FunctionMixin, pytest.Item):
return hash((self.parent, self.name)) return hash((self.parent, self.name))
def itemapi_property(name, set=False): class FuncargRequest:
prop = getattr(Function, name, None) """ A request for function arguments from a test function.
doc = getattr(prop, "__doc__", None)
def get(self):
return getattr(self._pyfuncitem, name)
if set:
def set(self, value):
setattr(self._pyfuncitem, name, value)
else:
set = None
return property(get, set, None, doc)
Note that there is an optional ``param`` attribute in case
class OldFuncargRequest(Request, PyobjContext): there was an invocation to metafunc.addcall(param=...).
""" (deprecated) helper interactions with a test function invocation. If no such call was done in a ``pytest_generate_tests``
hook, the attribute will not be present.
Note that there is an optional ``param`` attribute in case
there was an invocation to metafunc.addcall(param=...).
If no such call was done in a ``pytest_generate_tests``
hook, the attribute will not be present.
""" """
_argprefix = "pytest_funcarg__"
_argname = None
class LookupError(LookupError):
""" error on performing funcarg request. """
def __init__(self, pyfuncitem): def __init__(self, pyfuncitem):
self._pyfuncitem = pyfuncitem self._pyfuncitem = pyfuncitem
Request.extrainit(self) if hasattr(pyfuncitem, '_requestparam'):
self.funcargs = pyfuncitem.funcargs self.param = pyfuncitem._requestparam
self.getplugins = self._pyfuncitem.getplugins self.getparent = pyfuncitem.getparent
self.reportinfo = self._pyfuncitem.reportinfo self._funcargs = self._pyfuncitem.funcargs.copy()
self.getparent = self._pyfuncitem.getparent self._name2factory = {}
try: self._currentarg = None
self.param = self._pyfuncitem.param
except AttributeError:
pass
def __repr__(self): @cached_property
return "<OldFuncargRequest for %r>" % (self._pyfuncitem.name) def _plugins(self):
extra = [obj for obj in (self.module, self.instance) if obj]
return self._pyfuncitem.getplugins() + extra
_getscopeitem = itemapi_property("_getscopeitem")
funcargs = itemapi_property("funcargs", set=True)
keywords = itemapi_property("keywords")
config = itemapi_property("config")
session = itemapi_property("session")
fspath = itemapi_property("fspath")
applymarker = itemapi_property("applymarker")
@property @property
def function(self): def function(self):
""" function object of the test invocation. """
return self._pyfuncitem.obj return self._pyfuncitem.obj
@property
def keywords(self):
""" keywords of the test function item.
.. versionadded:: 2.0
"""
return self._pyfuncitem.keywords
@property
def module(self):
""" module where the test function was collected. """
return self._pyfuncitem.getparent(pytest.Module).obj
@property
def cls(self):
""" class (can be None) where the test function was collected. """
clscol = self._pyfuncitem.getparent(pytest.Class)
if clscol:
return clscol.obj
@property
def instance(self):
""" instance (can be None) on which test function was collected. """
return py.builtin._getimself(self.function)
@property
def config(self):
""" the pytest config object associated with this request. """
return self._pyfuncitem.config
@property
def fspath(self):
""" the file system path of the test module which collected this test. """
return self._pyfuncitem.fspath
def _fillfuncargs(self):
argnames = getfuncargnames(self.function)
if argnames:
assert not getattr(self._pyfuncitem, '_args', None), (
"yielded functions cannot have funcargs")
for argname in argnames:
if argname not in self._pyfuncitem.funcargs:
self._pyfuncitem.funcargs[argname] = self.getfuncargvalue(argname)
def applymarker(self, marker):
""" Apply a marker to a single test function invocation.
This method is useful if you don't want to have a keyword/marker
on all function invocations.
:arg marker: a :py:class:`_pytest.mark.MarkDecorator` object
created by a call to ``py.test.mark.NAME(...)``.
"""
if not isinstance(marker, py.test.mark.XYZ.__class__):
raise ValueError("%r is not a py.test.mark.* object")
self._pyfuncitem.keywords[marker.markname] = marker
def cached_setup(self, setup, teardown=None, scope="module", extrakey=None):
""" Return a testing resource managed by ``setup`` &
``teardown`` calls. ``scope`` and ``extrakey`` determine when the
``teardown`` function will be called so that subsequent calls to
``setup`` would recreate the resource.
:arg teardown: function receiving a previously setup resource.
:arg setup: a no-argument function creating a resource.
:arg scope: a string value out of ``function``, ``class``, ``module``
or ``session`` indicating the caching lifecycle of the resource.
:arg extrakey: added to internal caching key of (funcargname, scope).
"""
if not hasattr(self.config, '_setupcache'):
self.config._setupcache = {} # XXX weakref?
cachekey = (self._currentarg, self._getscopeitem(scope), extrakey)
cache = self.config._setupcache
try:
val = cache[cachekey]
except KeyError:
val = setup()
cache[cachekey] = val
if teardown is not None:
def finalizer():
del cache[cachekey]
teardown(val)
self._addfinalizer(finalizer, scope=scope)
return val
def getfuncargvalue(self, argname):
""" Retrieve a function argument by name for this test
function invocation. This allows one function argument factory
to call another function argument factory. If there are two
funcarg factories for the same test function argument the first
factory may use ``getfuncargvalue`` to call the second one and
do something additional with the resource.
"""
try:
return self._funcargs[argname]
except KeyError:
pass
if argname not in self._name2factory:
self._name2factory[argname] = self.config.pluginmanager.listattr(
plugins=self._plugins,
attrname=self._argprefix + str(argname)
)
#else: we are called recursively
if not self._name2factory[argname]:
self._raiselookupfailed(argname)
funcargfactory = self._name2factory[argname].pop()
oldarg = self._currentarg
mp = monkeypatch()
mp.setattr(self, '_currentarg', argname)
try:
param = self._pyfuncitem.callspec.getparam(argname)
except (AttributeError, ValueError):
pass
else:
mp.setattr(self, 'param', param, raising=False)
try:
self._funcargs[argname] = res = funcargfactory(request=self)
finally:
mp.undo()
return res
def _getscopeitem(self, scope):
if scope == "function":
return self._pyfuncitem
elif scope == "session":
return None
elif scope == "class":
x = self._pyfuncitem.getparent(pytest.Class)
if x is not None:
return x
scope = "module"
if scope == "module":
return self._pyfuncitem.getparent(pytest.Module)
raise ValueError("unknown finalization scope %r" %(scope,))
def addfinalizer(self, finalizer):
"""add finalizer function to be called after test function
finished execution. """
self._addfinalizer(finalizer, scope="function")
def _addfinalizer(self, finalizer, scope):
colitem = self._getscopeitem(scope)
self._pyfuncitem.session._setupstate.addfinalizer(
finalizer=finalizer, colitem=colitem)
def __repr__(self):
return "<FuncargRequest for %r>" %(self._pyfuncitem)
def _raiselookupfailed(self, argname):
available = []
for plugin in self._plugins:
for name in vars(plugin):
if name.startswith(self._argprefix):
name = name[len(self._argprefix):]
if name not in available:
available.append(name)
fspath, lineno, msg = self._pyfuncitem.reportinfo()
msg = "LookupError: no factory found for function argument %r" % (argname,)
msg += "\n available funcargs: %s" %(", ".join(available),)
msg += "\n use 'py.test --funcargs [testpath]' for help on them."
raise self.LookupError(msg)

View File

@ -54,15 +54,15 @@ def pytest_configure(config):
mp.setattr(config, '_tmpdirhandler', t, raising=False) mp.setattr(config, '_tmpdirhandler', t, raising=False)
mp.setattr(pytest, 'ensuretemp', t.ensuretemp, raising=False) mp.setattr(pytest, 'ensuretemp', t.ensuretemp, raising=False)
def pytest_funcarg__tmpdir(item): def pytest_funcarg__tmpdir(request):
"""return a temporary directory path object """return a temporary directory path object
which is unique to each test function invocation, which is unique to each test function invocation,
created as a sub directory of the base temporary created as a sub directory of the base temporary
directory. The returned object is a `py.path.local`_ directory. The returned object is a `py.path.local`_
path object. path object.
""" """
name = item.name name = request._pyfuncitem.name
name = py.std.re.sub("[\W]", "_", name) name = py.std.re.sub("[\W]", "_", name)
x = item.config._tmpdirhandler.mktemp(name, numbered=True) x = request.config._tmpdirhandler.mktemp(name, numbered=True)
return x return x

View File

@ -110,7 +110,7 @@ with a list of available function arguments.
The request object passed to factories The request object passed to factories
----------------------------------------- -----------------------------------------
Each funcarg factory receives a :py:class:`~_pytest.python.Request` object which Each funcarg factory receives a :py:class:`~_pytest.python.FuncargRequest` object which
provides methods to manage caching and finalization in the context of the provides methods to manage caching and finalization in the context of the
test invocation as well as several attributes of the the underlying test item. In fact, as of version pytest-2.3, the request API is implemented on all Item test invocation as well as several attributes of the the underlying test item. In fact, as of version pytest-2.3, the request API is implemented on all Item
objects and therefore the request object has general :py:class:`Node attributes and methods <_pytest.main.Node>` attributes. This is a backward compatible objects and therefore the request object has general :py:class:`Node attributes and methods <_pytest.main.Node>` attributes. This is a backward compatible

View File

@ -331,7 +331,7 @@ test execution:
Reference of objects involved in hooks Reference of objects involved in hooks
=========================================================== ===========================================================
.. autoclass:: _pytest.python.Request() .. autoclass:: _pytest.python.FuncargRequest()
:members: :members:
.. autoclass:: _pytest.config.Config() .. autoclass:: _pytest.config.Config()

View File

@ -277,14 +277,18 @@ class TestFunction:
def test_function_equality(self, testdir, tmpdir): def test_function_equality(self, testdir, tmpdir):
config = testdir.parseconfigure() config = testdir.parseconfigure()
session = testdir.Session(config) session = testdir.Session(config)
def func1():
pass
def func2():
pass
f1 = pytest.Function(name="name", config=config, f1 = pytest.Function(name="name", config=config,
args=(1,), callobj=isinstance, session=session) args=(1,), callobj=func1, session=session)
f2 = pytest.Function(name="name",config=config, f2 = pytest.Function(name="name",config=config,
args=(1,), callobj=py.builtin.callable, session=session) args=(1,), callobj=func2, session=session)
assert not f1 == f2 assert not f1 == f2
assert f1 != f2 assert f1 != f2
f3 = pytest.Function(name="name", config=config, f3 = pytest.Function(name="name", config=config,
args=(1,2), callobj=py.builtin.callable, session=session) args=(1,2), callobj=func2, session=session)
assert not f3 == f2 assert not f3 == f2
assert f3 != f2 assert f3 != f2
@ -292,7 +296,7 @@ class TestFunction:
assert f3 != f1 assert f3 != f1
f1_b = pytest.Function(name="name", config=config, f1_b = pytest.Function(name="name", config=config,
args=(1,), callobj=isinstance, session=session) args=(1,), callobj=func1, session=session)
assert f1 == f1_b assert f1 == f1_b
assert not f1 != f1_b assert not f1 != f1_b
@ -307,10 +311,12 @@ class TestFunction:
funcargs = {} funcargs = {}
id = "world" id = "world"
session = testdir.Session(config) session = testdir.Session(config)
def func():
pass
f5 = pytest.Function(name="name", config=config, f5 = pytest.Function(name="name", config=config,
callspec=callspec1, callobj=isinstance, session=session) callspec=callspec1, callobj=func, session=session)
f5b = pytest.Function(name="name", config=config, f5b = pytest.Function(name="name", config=config,
callspec=callspec2, callobj=isinstance, session=session) callspec=callspec2, callobj=func, session=session)
assert f5 != f5b assert f5 != f5b
assert not (f5 == f5b) assert not (f5 == f5b)
@ -549,7 +555,7 @@ class TestFillFuncArgs:
return 42 return 42
""") """)
item = testdir.getitem("def test_func(some): pass") item = testdir.getitem("def test_func(some): pass")
exc = pytest.raises(funcargs.OldFuncargRequest.LookupError, exc = pytest.raises(funcargs.FuncargRequest.LookupError,
"funcargs.fillfuncargs(item)") "funcargs.fillfuncargs(item)")
s = str(exc.value) s = str(exc.value)
assert s.find("xyzsomething") != -1 assert s.find("xyzsomething") != -1
@ -624,7 +630,7 @@ class TestRequest:
def pytest_funcarg__something(request): pass def pytest_funcarg__something(request): pass
def test_func(something): pass def test_func(something): pass
""") """)
req = funcargs.OldFuncargRequest(item) req = funcargs.FuncargRequest(item)
assert req.function == item.obj assert req.function == item.obj
assert req.keywords is item.keywords assert req.keywords is item.keywords
assert hasattr(req.module, 'test_func') assert hasattr(req.module, 'test_func')
@ -639,7 +645,7 @@ class TestRequest:
def test_func(self, something): def test_func(self, something):
pass pass
""") """)
req = funcargs.OldFuncargRequest(item) req = funcargs.FuncargRequest(item)
assert req.cls.__name__ == "TestB" assert req.cls.__name__ == "TestB"
assert req.instance.__class__ == req.cls assert req.instance.__class__ == req.cls
@ -653,7 +659,7 @@ class TestRequest:
""") """)
item1, = testdir.genitems([modcol]) item1, = testdir.genitems([modcol])
assert item1.name == "test_method" assert item1.name == "test_method"
name2factory = funcargs.OldFuncargRequest(item1)._name2factory name2factory = funcargs.FuncargRequest(item1)._name2factory
assert len(name2factory) == 1 assert len(name2factory) == 1
assert name2factory[0].__name__ == "pytest_funcarg__something" assert name2factory[0].__name__ == "pytest_funcarg__something"
@ -668,7 +674,7 @@ class TestRequest:
def test_func(something): def test_func(something):
assert something == 2 assert something == 2
""") """)
req = funcargs.OldFuncargRequest(item) req = funcargs.FuncargRequest(item)
val = req.getfuncargvalue("something") val = req.getfuncargvalue("something")
assert val == 2 assert val == 2
@ -680,7 +686,7 @@ class TestRequest:
return l.pop() return l.pop()
def test_func(something): pass def test_func(something): pass
""") """)
req = funcargs.OldFuncargRequest(item) req = funcargs.FuncargRequest(item)
pytest.raises(req.LookupError, req.getfuncargvalue, "notexists") pytest.raises(req.LookupError, req.getfuncargvalue, "notexists")
val = req.getfuncargvalue("something") val = req.getfuncargvalue("something")
assert val == 1 assert val == 1
@ -691,7 +697,8 @@ class TestRequest:
val2 = req.getfuncargvalue("other") # see about caching val2 = req.getfuncargvalue("other") # see about caching
assert val2 == 2 assert val2 == 2
pytest._fillfuncargs(item) pytest._fillfuncargs(item)
assert item.funcargs == {'something': 1, "other": 2} assert item.funcargs == {'something': 1}
#assert item.funcargs == {'something': 1, "other": 2}
def test_request_addfinalizer(self, testdir): def test_request_addfinalizer(self, testdir):
item = testdir.getitem(""" item = testdir.getitem("""
@ -728,7 +735,7 @@ class TestRequest:
def test_request_getmodulepath(self, testdir): def test_request_getmodulepath(self, testdir):
modcol = testdir.getmodulecol("def test_somefunc(): pass") modcol = testdir.getmodulecol("def test_somefunc(): pass")
item, = testdir.genitems([modcol]) item, = testdir.genitems([modcol])
req = funcargs.OldFuncargRequest(item) req = funcargs.FuncargRequest(item)
assert req.fspath == modcol.fspath assert req.fspath == modcol.fspath
def test_applymarker(testdir): def test_applymarker(testdir):
@ -739,7 +746,7 @@ def test_applymarker(testdir):
def test_func2(self, something): def test_func2(self, something):
pass pass
""") """)
req1 = funcargs.OldFuncargRequest(item1) req1 = funcargs.FuncargRequest(item1)
assert 'xfail' not in item1.keywords assert 'xfail' not in item1.keywords
req1.applymarker(pytest.mark.xfail) req1.applymarker(pytest.mark.xfail)
assert 'xfail' in item1.keywords assert 'xfail' in item1.keywords
@ -757,7 +764,7 @@ class TestRequestCachedSetup:
def test_func2(self, something): def test_func2(self, something):
pass pass
""") """)
req1 = funcargs.OldFuncargRequest(item1) req1 = funcargs.FuncargRequest(item1)
l = ["hello"] l = ["hello"]
def setup(): def setup():
return l.pop() return l.pop()
@ -766,7 +773,7 @@ class TestRequestCachedSetup:
assert ret1 == "hello" assert ret1 == "hello"
ret1b = req1.cached_setup(setup) ret1b = req1.cached_setup(setup)
assert ret1 == ret1b assert ret1 == ret1b
req2 = funcargs.OldFuncargRequest(item2) req2 = funcargs.FuncargRequest(item2)
ret2 = req2.cached_setup(setup) ret2 = req2.cached_setup(setup)
assert ret2 == ret1 assert ret2 == ret1
@ -782,7 +789,7 @@ class TestRequestCachedSetup:
def test_func2b(self, something): def test_func2b(self, something):
pass pass
""") """)
req1 = funcargs.OldFuncargRequest(item2) req1 = funcargs.FuncargRequest(item2)
l = ["hello2", "hello"] l = ["hello2", "hello"]
def setup(): def setup():
return l.pop() return l.pop()
@ -791,22 +798,22 @@ class TestRequestCachedSetup:
# automatically turn "class" to "module" scope # automatically turn "class" to "module" scope
ret1 = req1.cached_setup(setup, scope="class") ret1 = req1.cached_setup(setup, scope="class")
assert ret1 == "hello" assert ret1 == "hello"
req2 = funcargs.OldFuncargRequest(item2) req2 = funcargs.FuncargRequest(item2)
ret2 = req2.cached_setup(setup, scope="class") ret2 = req2.cached_setup(setup, scope="class")
assert ret2 == "hello" assert ret2 == "hello"
req3 = funcargs.OldFuncargRequest(item3) req3 = funcargs.FuncargRequest(item3)
ret3a = req3.cached_setup(setup, scope="class") ret3a = req3.cached_setup(setup, scope="class")
ret3b = req3.cached_setup(setup, scope="class") ret3b = req3.cached_setup(setup, scope="class")
assert ret3a == "hello2" assert ret3a == "hello2"
assert ret3b == "hello2" assert ret3b == "hello2"
req4 = funcargs.OldFuncargRequest(item4) req4 = funcargs.FuncargRequest(item4)
ret4 = req4.cached_setup(setup, scope="class") ret4 = req4.cached_setup(setup, scope="class")
assert ret4 == ret3a assert ret4 == ret3a
def test_request_cachedsetup_extrakey(self, testdir): def test_request_cachedsetup_extrakey(self, testdir):
item1 = testdir.getitem("def test_func(): pass") item1 = testdir.getitem("def test_func(): pass")
req1 = funcargs.OldFuncargRequest(item1) req1 = funcargs.FuncargRequest(item1)
l = ["hello", "world"] l = ["hello", "world"]
def setup(): def setup():
return l.pop() return l.pop()
@ -821,7 +828,7 @@ class TestRequestCachedSetup:
def test_request_cachedsetup_cache_deletion(self, testdir): def test_request_cachedsetup_cache_deletion(self, testdir):
item1 = testdir.getitem("def test_func(): pass") item1 = testdir.getitem("def test_func(): pass")
req1 = funcargs.OldFuncargRequest(item1) req1 = funcargs.FuncargRequest(item1)
l = [] l = []
def setup(): def setup():
l.append("setup") l.append("setup")
@ -1093,9 +1100,9 @@ class TestMetafuncFunctional:
def pytest_generate_tests(metafunc): def pytest_generate_tests(metafunc):
metafunc.addcall(param=metafunc) metafunc.addcall(param=metafunc)
def pytest_funcarg__metafunc(item): def pytest_funcarg__metafunc(request):
assert item._genid == "0" assert request._pyfuncitem._genid == "0"
return item.param return request.param
def test_function(metafunc, pytestconfig): def test_function(metafunc, pytestconfig):
assert metafunc.config == pytestconfig assert metafunc.config == pytestconfig
@ -1591,6 +1598,7 @@ def test_issue117_sessionscopeteardown(testdir):
]) ])
class TestRequestAPI: class TestRequestAPI:
@pytest.mark.xfail(reason="reverted refactoring")
def test_addfinalizer_cachedsetup_getfuncargvalue(self, testdir): def test_addfinalizer_cachedsetup_getfuncargvalue(self, testdir):
testdir.makeconftest(""" testdir.makeconftest("""
l = [] l = []
@ -1615,10 +1623,11 @@ class TestRequestAPI:
"*2 passed*", "*2 passed*",
]) ])
@pytest.mark.xfail(reason="consider item's funcarg access and error conditions")
def test_runtest_setup_sees_filled_funcargs(self, testdir): def test_runtest_setup_sees_filled_funcargs(self, testdir):
testdir.makeconftest(""" testdir.makeconftest("""
def pytest_runtest_setup(item): def pytest_runtest_setup(item):
assert item.funcargs is None assert not hasattr(item, "_request")
""") """)
testdir.makepyfile(""" testdir.makepyfile("""
def pytest_funcarg__a(request): def pytest_funcarg__a(request):