mid-scale refactoring to make request API available directly on items.
This commit was slightly tricky because i want to backward compatibility especially for the oejskit plugin which uses Funcarg-filling for non-Function objects.
This commit is contained in:
parent
227d847216
commit
91b6f2bda8
16
CHANGELOG
16
CHANGELOG
|
@ -1,6 +1,19 @@
|
|||
Changes between 2.2.4 and 2.2.5.dev
|
||||
Changes between 2.2.4 and 2.3.0.dev
|
||||
-----------------------------------
|
||||
|
||||
- merge FuncargRequest and Item API such that funcarg-functionality
|
||||
is now natively available on the "item" object passed to the various
|
||||
pytest_runtest hooks. This allows more sensitive behaviour
|
||||
of e.g. the pytest-django plugin which previously had no full
|
||||
access to all instantiated funcargs.
|
||||
This internal API re-organisation is a fully backward compatible
|
||||
change: existing factories accepting a "request" object will
|
||||
get a Function "item" object which carries the same API. In fact,
|
||||
the FuncargRequest API (or rather then a ResourceRequestAPI)
|
||||
could be available for all collection and item nodes but this is
|
||||
left for later consideration because it would render the documentation
|
||||
invalid and the "funcarg" naming sounds odd in context of
|
||||
directory, file, class, etc. nodes.
|
||||
- catch unicode-issues when writing failure representations
|
||||
to terminal to prevent the whole session from crashing
|
||||
- fix xfail/skip confusion: a skip-mark or an imperative pytest.skip
|
||||
|
@ -23,6 +36,7 @@ Changes between 2.2.4 and 2.2.5.dev
|
|||
pytest-django, trial and unittest integration.
|
||||
|
||||
- reporting refinements:
|
||||
|
||||
- pytest_report_header now receives a "startdir" so that
|
||||
you can use startdir.bestrelpath(yourpath) to show
|
||||
nice relative path
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
#
|
||||
__version__ = '2.2.5.dev4'
|
||||
__version__ = '2.3.0.dev1'
|
||||
|
|
|
@ -119,7 +119,7 @@ class CaptureManager:
|
|||
return "", ""
|
||||
|
||||
def activate_funcargs(self, pyfuncitem):
|
||||
if hasattr(pyfuncitem, 'funcargs'):
|
||||
if pyfuncitem.funcargs:
|
||||
for name, capfuncarg in pyfuncitem.funcargs.items():
|
||||
if name in ('capsys', 'capfd'):
|
||||
assert not hasattr(self, '_capturing_funcarg')
|
||||
|
@ -186,7 +186,7 @@ def pytest_funcarg__capsys(request):
|
|||
captured output available via ``capsys.readouterr()`` method calls
|
||||
which return a ``(out, err)`` tuple.
|
||||
"""
|
||||
if "capfd" in request._funcargs:
|
||||
if "capfd" in request.funcargs:
|
||||
raise request.LookupError(error_capsysfderror)
|
||||
return CaptureFuncarg(py.io.StdCapture)
|
||||
|
||||
|
@ -195,7 +195,7 @@ def pytest_funcarg__capfd(request):
|
|||
captured output available via ``capsys.readouterr()`` method calls
|
||||
which return a ``(out, err)`` tuple.
|
||||
"""
|
||||
if "capsys" in request._funcargs:
|
||||
if "capsys" in request.funcargs:
|
||||
raise request.LookupError(error_capsysfderror)
|
||||
if not hasattr(os, 'dup'):
|
||||
pytest.skip("capfd funcarg needs os.dup")
|
||||
|
|
177
_pytest/main.py
177
_pytest/main.py
|
@ -3,6 +3,8 @@
|
|||
import py
|
||||
import pytest, _pytest
|
||||
import os, sys, imp
|
||||
from _pytest.monkeypatch import monkeypatch
|
||||
|
||||
tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
|
||||
|
||||
# exitcodes for the command line
|
||||
|
@ -144,33 +146,162 @@ class HookProxy:
|
|||
|
||||
def compatproperty(name):
|
||||
def fget(self):
|
||||
# deprecated - use pytest.name
|
||||
return getattr(pytest, name)
|
||||
|
||||
return property(fget, None, None,
|
||||
"deprecated attribute %r, use pytest.%s" % (name, name))
|
||||
return property(fget)
|
||||
|
||||
def pyobj_property(name):
|
||||
def get(self):
|
||||
node = self.getparent(getattr(pytest, name))
|
||||
if node is not None:
|
||||
return node.obj
|
||||
doc = "python %s object this node was collected from (can be None)." % (
|
||||
name.lower(),)
|
||||
return property(get, None, None, doc)
|
||||
|
||||
class Request(object):
|
||||
_argprefix = "pytest_funcarg__"
|
||||
|
||||
class LookupError(LookupError):
|
||||
""" error while performing funcarg factory lookup. """
|
||||
|
||||
def _initattr(self):
|
||||
self._name2factory = {}
|
||||
self._currentarg = None
|
||||
|
||||
@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
|
||||
the same request object and can itself perform recursive
|
||||
calls to this method, effectively allowing to make use of
|
||||
multiple other funcarg values or to decorate values from
|
||||
other name-matching factories.
|
||||
"""
|
||||
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)
|
||||
|
||||
class Node(object):
|
||||
""" base class for all Nodes in the collection tree.
|
||||
""" base class for Collector and Item the test collection tree.
|
||||
Collector subclasses have children, Items are terminal nodes."""
|
||||
|
||||
def __init__(self, name, parent=None, config=None, session=None):
|
||||
#: a unique name with the scope of the parent
|
||||
#: a unique name within the scope of the parent node
|
||||
self.name = name
|
||||
|
||||
#: the parent collector node.
|
||||
self.parent = parent
|
||||
|
||||
#: the test config object
|
||||
#: the pytest config object
|
||||
self.config = config or parent.config
|
||||
|
||||
#: the collection this node is part of
|
||||
#: the session this node is part of
|
||||
self.session = session or parent.session
|
||||
|
||||
#: filesystem path where this node was collected from
|
||||
#: filesystem path where this node was collected from (can be None)
|
||||
self.fspath = getattr(parent, 'fspath', None)
|
||||
self.ihook = self.session.gethookproxy(self.fspath)
|
||||
|
||||
#: keywords on this node (node name is always contained)
|
||||
self.keywords = {self.name: True}
|
||||
|
||||
#: fspath sensitive hook proxy used to call pytest hooks
|
||||
self.ihook = self.session.gethookproxy(self.fspath)
|
||||
|
||||
Module = compatproperty("Module")
|
||||
Class = compatproperty("Class")
|
||||
Instance = compatproperty("Instance")
|
||||
|
@ -178,6 +309,11 @@ class Node(object):
|
|||
File = compatproperty("File")
|
||||
Item = compatproperty("Item")
|
||||
|
||||
module = pyobj_property("Module")
|
||||
cls = pyobj_property("Class")
|
||||
instance = pyobj_property("Instance")
|
||||
|
||||
|
||||
def _getcustomclass(self, name):
|
||||
cls = getattr(self, name)
|
||||
if cls != getattr(pytest, name):
|
||||
|
@ -193,12 +329,14 @@ class Node(object):
|
|||
# methods for ordering nodes
|
||||
@property
|
||||
def nodeid(self):
|
||||
""" a ::-separated string denoting its collection tree address. """
|
||||
try:
|
||||
return self._nodeid
|
||||
except AttributeError:
|
||||
self._nodeid = x = self._makeid()
|
||||
return x
|
||||
|
||||
|
||||
def _makeid(self):
|
||||
return self.parent.nodeid + "::" + self.name
|
||||
|
||||
|
@ -338,15 +476,36 @@ class FSCollector(Collector):
|
|||
class File(FSCollector):
|
||||
""" base class for collecting tests from a file. """
|
||||
|
||||
class Item(Node):
|
||||
class Item(Node, Request):
|
||||
""" a basic test invocation item. Note that for a single function
|
||||
there might be multiple test invocation items.
|
||||
"""
|
||||
nextitem = None
|
||||
|
||||
def __init__(self, name, parent=None, config=None, session=None):
|
||||
super(Item, self).__init__(name, parent, config, session)
|
||||
self._initattr()
|
||||
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.
|
||||
|
||||
def reportinfo(self):
|
||||
return self.fspath, None, ""
|
||||
|
||||
def applymarker(self, marker):
|
||||
""" Apply a marker to this item. This method is
|
||||
useful if you have several parametrized function
|
||||
and want to mark a single one of them.
|
||||
|
||||
:arg marker: a :py:class:`_pytest.mark.MarkDecorator` object
|
||||
created by a call to ``py.test.mark.NAME(...)``.
|
||||
"""
|
||||
if not isinstance(marker, pytest.mark.XYZ.__class__):
|
||||
raise ValueError("%r is not a py.test.mark.* object")
|
||||
self.keywords[marker.markname] = marker
|
||||
|
||||
|
||||
@property
|
||||
def location(self):
|
||||
try:
|
||||
|
|
|
@ -318,7 +318,7 @@ class TmpTestdir:
|
|||
# used from runner functional tests
|
||||
item = self.getitem(source)
|
||||
# the test class where we are called from wants to provide the runner
|
||||
testclassinstance = py.builtin._getimself(self.request.function)
|
||||
testclassinstance = self.request.instance
|
||||
runner = testclassinstance.getrunner()
|
||||
return runner(item)
|
||||
|
||||
|
|
|
@ -4,11 +4,27 @@ import inspect
|
|||
import sys
|
||||
import pytest
|
||||
from py._code.code import TerminalRepr
|
||||
from _pytest.monkeypatch import monkeypatch
|
||||
from _pytest.main import Request, Item
|
||||
|
||||
import _pytest
|
||||
cutdir = py.path.local(_pytest.__file__).dirpath()
|
||||
|
||||
def cached_property(f):
|
||||
"""returns a cached property that is calculated by function f.
|
||||
taken from http://code.activestate.com/recipes/576563-cached-property/"""
|
||||
def get(self):
|
||||
try:
|
||||
return self._property_cache[f]
|
||||
except AttributeError:
|
||||
self._property_cache = {}
|
||||
x = self._property_cache[f] = f(self)
|
||||
return x
|
||||
except KeyError:
|
||||
x = self._property_cache[f] = f(self)
|
||||
return x
|
||||
return property(get)
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("general")
|
||||
group.addoption('--funcargs',
|
||||
|
@ -60,13 +76,21 @@ def pytest_funcarg__pytestconfig(request):
|
|||
""" the pytest config object with access to command line opts."""
|
||||
return request.config
|
||||
|
||||
|
||||
def pytest_pyfunc_call(__multicall__, pyfuncitem):
|
||||
if not __multicall__.execute():
|
||||
testfunction = pyfuncitem.obj
|
||||
if pyfuncitem._isyieldedfunction():
|
||||
testfunction(*pyfuncitem._args)
|
||||
else:
|
||||
funcargs = pyfuncitem.funcargs
|
||||
try:
|
||||
funcargnames = pyfuncitem.funcargnames
|
||||
except AttributeError:
|
||||
funcargs = pyfuncitem.funcargs
|
||||
else:
|
||||
funcargs = {}
|
||||
for name in funcargnames:
|
||||
funcargs[name] = pyfuncitem.funcargs[name]
|
||||
testfunction(**funcargs)
|
||||
|
||||
def pytest_collect_file(path, parent):
|
||||
|
@ -110,6 +134,7 @@ def is_generator(func):
|
|||
return False
|
||||
|
||||
class PyobjMixin(object):
|
||||
|
||||
def obj():
|
||||
def fget(self):
|
||||
try:
|
||||
|
@ -232,12 +257,15 @@ class PyCollectorMixin(PyobjMixin, pytest.Collector):
|
|||
gentesthook.pcall(plugins, metafunc=metafunc)
|
||||
Function = self._getcustomclass("Function")
|
||||
if not metafunc._calls:
|
||||
return Function(name, parent=self)
|
||||
return Function(name, parent=self,
|
||||
funcargnames=metafunc.funcargnames)
|
||||
l = []
|
||||
for callspec in metafunc._calls:
|
||||
subname = "%s[%s]" %(name, callspec.id)
|
||||
function = Function(name=subname, parent=self,
|
||||
callspec=callspec, callobj=funcobj, keywords={callspec.id:True})
|
||||
callspec=callspec, callobj=funcobj,
|
||||
funcargnames=metafunc.funcargnames,
|
||||
keywords={callspec.id:True})
|
||||
l.append(function)
|
||||
return l
|
||||
|
||||
|
@ -256,6 +284,7 @@ def transfer_markers(funcobj, cls, mod):
|
|||
pytestmark(funcobj)
|
||||
|
||||
class Module(pytest.File, PyCollectorMixin):
|
||||
""" Collector for test classes and functions. """
|
||||
def _getobj(self):
|
||||
return self._memoizedcall('_obj', self._importtestmodule)
|
||||
|
||||
|
@ -303,7 +332,7 @@ class Module(pytest.File, PyCollectorMixin):
|
|||
self.obj.teardown_module()
|
||||
|
||||
class Class(PyCollectorMixin, pytest.Collector):
|
||||
|
||||
""" Collector for test methods. """
|
||||
def collect(self):
|
||||
return [self._getcustomclass("Instance")(name="()", parent=self)]
|
||||
|
||||
|
@ -373,7 +402,7 @@ class FunctionMixin(PyobjMixin):
|
|||
excinfo.traceback = ntraceback.filter()
|
||||
|
||||
def _repr_failure_py(self, excinfo, style="long"):
|
||||
if excinfo.errisinstance(FuncargRequest.LookupError):
|
||||
if excinfo.errisinstance(Request.LookupError):
|
||||
fspath, lineno, msg = self.reportinfo()
|
||||
lines, _ = inspect.getsourcelines(self.obj)
|
||||
for i, line in enumerate(lines):
|
||||
|
@ -445,77 +474,6 @@ class Generator(FunctionMixin, PyCollectorMixin, pytest.Collector):
|
|||
return name, call, args
|
||||
|
||||
|
||||
#
|
||||
# Test Items
|
||||
#
|
||||
_dummy = object()
|
||||
class Function(FunctionMixin, pytest.Item):
|
||||
""" a Function Item is responsible for setting up
|
||||
and executing a Python callable test object.
|
||||
"""
|
||||
_genid = None
|
||||
def __init__(self, name, parent=None, args=None, config=None,
|
||||
callspec=None, callobj=_dummy, keywords=None, session=None):
|
||||
super(Function, self).__init__(name, parent,
|
||||
config=config, session=session)
|
||||
self._args = args
|
||||
if self._isyieldedfunction():
|
||||
assert not callspec, (
|
||||
"yielded functions (deprecated) cannot have funcargs")
|
||||
else:
|
||||
if callspec is not None:
|
||||
self.callspec = callspec
|
||||
self.funcargs = callspec.funcargs or {}
|
||||
self._genid = callspec.id
|
||||
if hasattr(callspec, "param"):
|
||||
self._requestparam = callspec.param
|
||||
else:
|
||||
self.funcargs = {}
|
||||
if callobj is not _dummy:
|
||||
self._obj = callobj
|
||||
self.function = getattr(self.obj, 'im_func', self.obj)
|
||||
self.keywords.update(py.builtin._getfuncdict(self.obj) or {})
|
||||
if keywords:
|
||||
self.keywords.update(keywords)
|
||||
|
||||
def _getobj(self):
|
||||
name = self.name
|
||||
i = name.find("[") # parametrization
|
||||
if i != -1:
|
||||
name = name[:i]
|
||||
return getattr(self.parent.obj, name)
|
||||
|
||||
def _isyieldedfunction(self):
|
||||
return self._args is not None
|
||||
|
||||
def runtest(self):
|
||||
""" execute the underlying test function. """
|
||||
self.ihook.pytest_pyfunc_call(pyfuncitem=self)
|
||||
|
||||
def setup(self):
|
||||
super(Function, self).setup()
|
||||
if hasattr(self, 'funcargs'):
|
||||
fillfuncargs(self)
|
||||
|
||||
def __eq__(self, other):
|
||||
try:
|
||||
return (self.name == other.name and
|
||||
self._args == other._args and
|
||||
self.parent == other.parent and
|
||||
self.obj == other.obj and
|
||||
getattr(self, '_genid', None) ==
|
||||
getattr(other, '_genid', None)
|
||||
)
|
||||
except AttributeError:
|
||||
pass
|
||||
return False
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.parent, self.name))
|
||||
|
||||
def hasinit(obj):
|
||||
init = getattr(obj, '__init__', None)
|
||||
if init:
|
||||
|
@ -535,10 +493,20 @@ def getfuncargnames(function, startindex=None):
|
|||
return argnames[startindex:-numdefaults]
|
||||
return argnames[startindex:]
|
||||
|
||||
def fillfuncargs(function):
|
||||
def fillfuncargs(node):
|
||||
""" fill missing funcargs. """
|
||||
request = FuncargRequest(pyfuncitem=function)
|
||||
request._fillfuncargs()
|
||||
if not isinstance(node, Function):
|
||||
node = FuncargRequest(pyfuncitem=node)
|
||||
if node.funcargs is None:
|
||||
node.funcargs = getattr(node, "_funcargs", {})
|
||||
if not isinstance(node, Function) or not node._isyieldedfunction():
|
||||
try:
|
||||
funcargnames = node.funcargnames
|
||||
except AttributeError:
|
||||
funcargnames = getfuncargnames(node.function)
|
||||
if funcargnames:
|
||||
for argname in funcargnames:
|
||||
node.getfuncargvalue(argname)
|
||||
|
||||
_notexists = object()
|
||||
|
||||
|
@ -697,195 +665,6 @@ class IDMaker:
|
|||
l.append(str(val))
|
||||
return "-".join(l)
|
||||
|
||||
class FuncargRequest:
|
||||
""" A request for function arguments from a test function.
|
||||
|
||||
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):
|
||||
self._pyfuncitem = pyfuncitem
|
||||
if hasattr(pyfuncitem, '_requestparam'):
|
||||
self.param = pyfuncitem._requestparam
|
||||
extra = [obj for obj in (self.module, self.instance) if obj]
|
||||
self._plugins = pyfuncitem.getplugins() + extra
|
||||
self._funcargs = self._pyfuncitem.funcargs.copy()
|
||||
self._name2factory = {}
|
||||
self._currentarg = None
|
||||
|
||||
@property
|
||||
def function(self):
|
||||
""" function object of the test invocation. """
|
||||
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)
|
||||
|
||||
def showfuncargs(config):
|
||||
from _pytest.main import wrap_session
|
||||
|
@ -903,8 +682,8 @@ def _showfuncargs_main(config, session):
|
|||
for plugin in plugins:
|
||||
available = []
|
||||
for name, factory in vars(plugin).items():
|
||||
if name.startswith(FuncargRequest._argprefix):
|
||||
name = name[len(FuncargRequest._argprefix):]
|
||||
if name.startswith(Request._argprefix):
|
||||
name = name[len(Request._argprefix):]
|
||||
if name not in available:
|
||||
available.append([name, factory])
|
||||
if available:
|
||||
|
@ -1009,3 +788,131 @@ class RaisesContext(object):
|
|||
self.excinfo.__init__(tp)
|
||||
return issubclass(self.excinfo.type, self.ExpectedException)
|
||||
|
||||
#
|
||||
# the basic py.test Function item
|
||||
#
|
||||
_dummy = object()
|
||||
class Function(FunctionMixin, pytest.Item):
|
||||
""" a Function Item is responsible for setting up and executing a
|
||||
Python test function.
|
||||
"""
|
||||
_genid = None
|
||||
def __init__(self, name, parent=None, args=None, config=None,
|
||||
callspec=None, callobj=_dummy, keywords=None,
|
||||
session=None, funcargnames=()):
|
||||
super(Function, self).__init__(name, parent, config=config,
|
||||
session=session)
|
||||
self.funcargnames = funcargnames
|
||||
self._args = args
|
||||
if self._isyieldedfunction():
|
||||
assert not callspec, (
|
||||
"yielded functions (deprecated) cannot have funcargs")
|
||||
else:
|
||||
if callspec is not None:
|
||||
self.callspec = callspec
|
||||
self._funcargs = callspec.funcargs or {}
|
||||
self._genid = callspec.id
|
||||
if hasattr(callspec, "param"):
|
||||
self.param = callspec.param
|
||||
if callobj is not _dummy:
|
||||
self._obj = callobj
|
||||
|
||||
self.keywords.update(py.builtin._getfuncdict(self.obj) or {})
|
||||
if keywords:
|
||||
self.keywords.update(keywords)
|
||||
|
||||
@property
|
||||
def function(self):
|
||||
"underlying python 'function' object"
|
||||
return getattr(self.obj, 'im_func', self.obj)
|
||||
|
||||
def _getobj(self):
|
||||
name = self.name
|
||||
i = name.find("[") # parametrization
|
||||
if i != -1:
|
||||
name = name[:i]
|
||||
return getattr(self.parent.obj, name)
|
||||
|
||||
@property
|
||||
def _pyfuncitem(self):
|
||||
"(compatonly) for code expecting pytest-2.2 style request objects"
|
||||
return self
|
||||
|
||||
def _isyieldedfunction(self):
|
||||
return getattr(self, "_args", None) is not None
|
||||
|
||||
def runtest(self):
|
||||
""" execute the underlying test function. """
|
||||
self.ihook.pytest_pyfunc_call(pyfuncitem=self)
|
||||
|
||||
def setup(self):
|
||||
super(Function, self).setup()
|
||||
fillfuncargs(self)
|
||||
|
||||
def __eq__(self, other):
|
||||
try:
|
||||
return (self.name == other.name and
|
||||
self._args == other._args and
|
||||
self.parent == other.parent and
|
||||
self.obj == other.obj and
|
||||
getattr(self, '_genid', None) ==
|
||||
getattr(other, '_genid', None)
|
||||
)
|
||||
except AttributeError:
|
||||
pass
|
||||
return False
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.parent, self.name))
|
||||
|
||||
|
||||
def itemapi_property(name, set=False):
|
||||
prop = getattr(Function, name, None)
|
||||
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)
|
||||
|
||||
|
||||
class FuncargRequest(Request):
|
||||
""" (deprecated) helper interactions with a test function invocation.
|
||||
|
||||
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.
|
||||
"""
|
||||
def __init__(self, pyfuncitem):
|
||||
self._pyfuncitem = pyfuncitem
|
||||
Request._initattr(self)
|
||||
self.getplugins = self._pyfuncitem.getplugins
|
||||
self.reportinfo = self._pyfuncitem.reportinfo
|
||||
try:
|
||||
self.param = self._pyfuncitem.param
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def __repr__(self):
|
||||
return "<FuncargRequest for %r>" % (self._pyfuncitem.name)
|
||||
|
||||
_getscopeitem = itemapi_property("_getscopeitem")
|
||||
funcargs = itemapi_property("funcargs", set=True)
|
||||
keywords = itemapi_property("keywords")
|
||||
module = itemapi_property("module")
|
||||
cls = itemapi_property("cls")
|
||||
instance = itemapi_property("instance")
|
||||
config = itemapi_property("config")
|
||||
session = itemapi_property("session")
|
||||
fspath = itemapi_property("fspath")
|
||||
applymarker = itemapi_property("applymarker")
|
||||
@property
|
||||
def function(self):
|
||||
return self._pyfuncitem.obj
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
""" (disabled by default) create result information in a plain text file. """
|
||||
""" log machine-parseable test session result information in a plain
|
||||
text file.
|
||||
"""
|
||||
|
||||
import py
|
||||
|
||||
|
|
|
@ -54,15 +54,15 @@ def pytest_configure(config):
|
|||
mp.setattr(config, '_tmpdirhandler', t, raising=False)
|
||||
mp.setattr(pytest, 'ensuretemp', t.ensuretemp, raising=False)
|
||||
|
||||
def pytest_funcarg__tmpdir(request):
|
||||
def pytest_funcarg__tmpdir(item):
|
||||
"""return a temporary directory path object
|
||||
which is unique to each test function invocation,
|
||||
created as a sub directory of the base temporary
|
||||
directory. The returned object is a `py.path.local`_
|
||||
path object.
|
||||
"""
|
||||
name = request._pyfuncitem.name
|
||||
name = item.name
|
||||
name = py.std.re.sub("[\W]", "_", name)
|
||||
x = request.config._tmpdirhandler.mktemp(name, numbered=True)
|
||||
x = item.config._tmpdirhandler.mktemp(name, numbered=True)
|
||||
return x
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
# The short X.Y version.
|
||||
version = release = "2.2.4.3"
|
||||
version = release = "2.3.0.dev1"
|
||||
|
||||
import sys, os
|
||||
|
||||
|
@ -26,6 +26,8 @@ import sys, os
|
|||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
autodoc_member_order = "bysource"
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
|
@ -53,6 +55,7 @@ project = u'pytest'
|
|||
copyright = u'2011, holger krekel et alii'
|
||||
|
||||
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
@ -78,7 +81,7 @@ exclude_patterns = ['links.inc', '_build', 'naming20.txt', 'test/*',
|
|||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
add_module_names = False
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
|
@ -87,6 +90,8 @@ exclude_patterns = ['links.inc', '_build', 'naming20.txt', 'test/*',
|
|||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
|
|
|
@ -11,26 +11,27 @@ Injecting objects into test functions (funcargs)
|
|||
Dependency injection through function arguments
|
||||
=================================================
|
||||
|
||||
py.test lets you inject objects into test functions and precisely
|
||||
control their life cycle in relation to the test execution. It is
|
||||
also possible to run a test function multiple times with different objects.
|
||||
py.test lets you inject objects into test invocations and precisely
|
||||
control their life cycle in relation to the overall test execution. Moreover,
|
||||
you can run a test function multiple times injecting different objects.
|
||||
|
||||
The basic mechanism for injecting objects is also called the
|
||||
*funcarg mechanism* because objects are ultimately injected
|
||||
by calling a test function with it as an argument. Unlike the
|
||||
classical xUnit approach *funcargs* relate more to `Dependency Injection`_
|
||||
because they help to de-couple test code from objects required for
|
||||
them to execute.
|
||||
them to execute. At test writing time you do not need to care for the
|
||||
details of how your required resources are constructed or if they
|
||||
live through a function, class, module or session scope.
|
||||
|
||||
.. _`Dependency injection`: http://en.wikipedia.org/wiki/Dependency_injection
|
||||
|
||||
To create a value with which to call a test function a factory function
|
||||
is called which gets full access to the test function context and can
|
||||
register finalizers or invoke lifecycle-caching helpers. The factory
|
||||
can be implemented in same test class or test module, or in a
|
||||
per-directory ``conftest.py`` file or even in an external plugin. This
|
||||
allows full de-coupling of test code and objects needed for test
|
||||
execution.
|
||||
can be implemented in same test class or test module, in a
|
||||
per-directory ``conftest.py`` file or in an external plugin. This
|
||||
allows total de-coupling of test and setup code.
|
||||
|
||||
A test function may be invoked multiple times in which case we
|
||||
speak of :ref:`parametrized testing <parametrizing-tests>`. This can be
|
||||
|
@ -38,7 +39,7 @@ very useful if you want to test e.g. against different database backends
|
|||
or with multiple numerical arguments sets and want to reuse the same set
|
||||
of test functions.
|
||||
|
||||
py.test comes with :ref:`builtinfuncargs` and there are some refined usages in the examples section.
|
||||
py.test comes with some :ref:`builtinfuncargs` and there are some refined usages in the examples section.
|
||||
|
||||
.. _funcarg:
|
||||
|
||||
|
@ -55,10 +56,8 @@ Let's look at a simple self-contained test module::
|
|||
assert myfuncarg == 17
|
||||
|
||||
This test function needs an injected object named ``myfuncarg``.
|
||||
py.test will discover and call the factory named
|
||||
``pytest_funcarg__myfuncarg`` within the same module in this case.
|
||||
|
||||
Running the test looks like this::
|
||||
py.test will automatically discover and call the ``pytest_funcarg__myfuncarg``
|
||||
factory. Running the test looks like this::
|
||||
|
||||
$ py.test test_simplefactory.py
|
||||
=========================== test session starts ============================
|
||||
|
@ -79,9 +78,9 @@ Running the test looks like this::
|
|||
test_simplefactory.py:5: AssertionError
|
||||
========================= 1 failed in 0.01 seconds =========================
|
||||
|
||||
This means that indeed the test function was called with a ``myfuncarg``
|
||||
argument value of ``42`` and the assert fails. Here is how py.test
|
||||
comes to call the test function this way:
|
||||
This shows that the test function was called with a ``myfuncarg``
|
||||
argument value of ``42`` and the assert fails as expected. Here is
|
||||
how py.test comes to call the test function this way:
|
||||
|
||||
1. py.test :ref:`finds <test discovery>` the ``test_function`` because
|
||||
of the ``test_`` prefix. The test function needs a function argument
|
||||
|
@ -99,13 +98,22 @@ Note that if you misspell a function argument or want
|
|||
to use one that isn't available, you'll see an error
|
||||
with a list of available function arguments.
|
||||
|
||||
You can always issue::
|
||||
.. Note::
|
||||
|
||||
py.test --funcargs test_simplefactory.py
|
||||
You can always issue::
|
||||
|
||||
to see available function arguments (which you can also
|
||||
think of as "resources").
|
||||
py.test --funcargs test_simplefactory.py
|
||||
|
||||
to see available function arguments.
|
||||
|
||||
The request object passed to factories
|
||||
-----------------------------------------
|
||||
|
||||
Each funcarg factory receives a :py:class:`~_pytest.main.Request` object which
|
||||
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
|
||||
objects and therefore the request object has general :py:class:`Node attributes and methods <_pytest.main.Node>` attributes. This is a backward compatible
|
||||
change so no changes are neccessary for pre-2.3 funcarg factories.
|
||||
|
||||
.. _`parametrizing tests, generalized`: http://tetamap.wordpress.com/2009/05/13/parametrizing-python-tests-generalized/
|
||||
|
||||
|
@ -116,27 +124,6 @@ think of as "resources").
|
|||
.. _`funcarg factory`:
|
||||
.. _factory:
|
||||
|
||||
The funcarg **request** object
|
||||
=============================================
|
||||
|
||||
Each funcarg factory receives a **request** object tied to a specific test
|
||||
function call. A request object is passed to a funcarg factory and provides
|
||||
access to test configuration and context:
|
||||
|
||||
.. autoclass:: _pytest.python.FuncargRequest()
|
||||
:members: function,cls,module,keywords,config
|
||||
|
||||
.. _`useful caching and finalization helpers`:
|
||||
|
||||
.. automethod:: FuncargRequest.addfinalizer
|
||||
|
||||
.. automethod:: FuncargRequest.cached_setup
|
||||
|
||||
.. automethod:: FuncargRequest.applymarker
|
||||
|
||||
.. automethod:: FuncargRequest.getfuncargvalue
|
||||
|
||||
|
||||
.. _`test generators`:
|
||||
.. _`parametrizing-tests`:
|
||||
.. _`parametrized test functions`:
|
||||
|
|
|
@ -296,7 +296,6 @@ into interactive debugging when a test failure occurs.
|
|||
|
||||
The :py:mod:`_pytest.terminal` reported specifically uses
|
||||
the reporting hook to print information about a test run.
|
||||
|
||||
Collection hooks
|
||||
------------------------------
|
||||
|
||||
|
@ -327,37 +326,44 @@ test execution:
|
|||
|
||||
.. autofunction: pytest_runtest_logreport
|
||||
|
||||
Reference of important objects involved in hooks
|
||||
Reference of objects involved in hooks
|
||||
===========================================================
|
||||
|
||||
.. autoclass:: _pytest.config.Config
|
||||
.. autoclass:: _pytest.main.Request()
|
||||
:members:
|
||||
|
||||
.. autoclass:: _pytest.config.Parser
|
||||
.. autoclass:: _pytest.config.Config()
|
||||
:members:
|
||||
|
||||
.. autoclass:: _pytest.main.Node(name, parent)
|
||||
.. autoclass:: _pytest.config.Parser()
|
||||
:members:
|
||||
|
||||
..
|
||||
.. autoclass:: _pytest.main.File(fspath, parent)
|
||||
:members:
|
||||
|
||||
.. autoclass:: _pytest.main.Item(name, parent)
|
||||
:members:
|
||||
|
||||
.. autoclass:: _pytest.python.Module(name, parent)
|
||||
:members:
|
||||
|
||||
.. autoclass:: _pytest.python.Class(name, parent)
|
||||
:members:
|
||||
|
||||
.. autoclass:: _pytest.python.Function(name, parent)
|
||||
:members:
|
||||
|
||||
.. autoclass:: _pytest.runner.CallInfo
|
||||
.. autoclass:: _pytest.main.Node()
|
||||
:members:
|
||||
|
||||
.. autoclass:: _pytest.runner.TestReport
|
||||
.. autoclass:: _pytest.main.Collector()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: _pytest.main.Item()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: _pytest.python.Module()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: _pytest.python.Class()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: _pytest.python.Function()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: _pytest.runner.CallInfo()
|
||||
:members:
|
||||
|
||||
.. autoclass:: _pytest.runner.TestReport()
|
||||
:members:
|
||||
|
||||
|
|
2
setup.py
2
setup.py
|
@ -24,7 +24,7 @@ def main():
|
|||
name='pytest',
|
||||
description='py.test: simple powerful testing with Python',
|
||||
long_description = long_description,
|
||||
version='2.2.5.dev4',
|
||||
version='2.3.0.dev1',
|
||||
url='http://pytest.org',
|
||||
license='MIT license',
|
||||
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
|
||||
|
|
|
@ -12,33 +12,25 @@ def interpret(expr):
|
|||
class TestBinReprIntegration:
|
||||
pytestmark = needsnewassert
|
||||
|
||||
def pytest_funcarg__hook(self, request):
|
||||
class MockHook(object):
|
||||
def __init__(self):
|
||||
self.called = False
|
||||
self.args = tuple()
|
||||
self.kwargs = dict()
|
||||
|
||||
def __call__(self, op, left, right):
|
||||
self.called = True
|
||||
self.op = op
|
||||
self.left = left
|
||||
self.right = right
|
||||
mockhook = MockHook()
|
||||
monkeypatch = request.getfuncargvalue("monkeypatch")
|
||||
monkeypatch.setattr(util, '_reprcompare', mockhook)
|
||||
return mockhook
|
||||
|
||||
def test_pytest_assertrepr_compare_called(self, hook):
|
||||
interpret('assert 0 == 1')
|
||||
assert hook.called
|
||||
|
||||
|
||||
def test_pytest_assertrepr_compare_args(self, hook):
|
||||
interpret('assert [0, 1] == [0, 2]')
|
||||
assert hook.op == '=='
|
||||
assert hook.left == [0, 1]
|
||||
assert hook.right == [0, 2]
|
||||
def test_pytest_assertrepr_compare_called(self, testdir):
|
||||
testdir.makeconftest("""
|
||||
l = []
|
||||
def pytest_assertrepr_compare(op, left, right):
|
||||
l.append((op, left, right))
|
||||
def pytest_funcarg__l(request):
|
||||
return l
|
||||
""")
|
||||
testdir.makepyfile("""
|
||||
def test_hello():
|
||||
assert 0 == 1
|
||||
def test_check(l):
|
||||
assert l == [("==", 0, 1)]
|
||||
""")
|
||||
result = testdir.runpytest("-v")
|
||||
result.stdout.fnmatch_lines([
|
||||
"*test_hello*FAIL*",
|
||||
"*test_check*PASS*",
|
||||
])
|
||||
|
||||
def callequal(left, right):
|
||||
return plugin.pytest_assertrepr_compare('==', left, right)
|
||||
|
|
|
@ -690,8 +690,8 @@ class TestRequest:
|
|||
assert val2 == 2
|
||||
val2 = req.getfuncargvalue("other") # see about caching
|
||||
assert val2 == 2
|
||||
req._fillfuncargs()
|
||||
assert item.funcargs == {'something': 1}
|
||||
pytest._fillfuncargs(item)
|
||||
assert item.funcargs == {'something': 1, "other": 2}
|
||||
|
||||
def test_request_addfinalizer(self, testdir):
|
||||
item = testdir.getitem("""
|
||||
|
@ -700,9 +700,8 @@ class TestRequest:
|
|||
request.addfinalizer(lambda: teardownlist.append(1))
|
||||
def test_func(something): pass
|
||||
""")
|
||||
req = funcargs.FuncargRequest(item)
|
||||
req._pyfuncitem.session._setupstate.prepare(item) # XXX
|
||||
req._fillfuncargs()
|
||||
item.session._setupstate.prepare(item)
|
||||
pytest._fillfuncargs(item)
|
||||
# successively check finalization calls
|
||||
teardownlist = item.getparent(pytest.Module).obj.teardownlist
|
||||
ss = item.session._setupstate
|
||||
|
@ -799,7 +798,8 @@ class TestRequestCachedSetup:
|
|||
req3 = funcargs.FuncargRequest(item3)
|
||||
ret3a = req3.cached_setup(setup, scope="class")
|
||||
ret3b = req3.cached_setup(setup, scope="class")
|
||||
assert ret3a == ret3b == "hello2"
|
||||
assert ret3a == "hello2"
|
||||
assert ret3b == "hello2"
|
||||
req4 = funcargs.FuncargRequest(item4)
|
||||
ret4 = req4.cached_setup(setup, scope="class")
|
||||
assert ret4 == ret3a
|
||||
|
@ -830,11 +830,12 @@ class TestRequestCachedSetup:
|
|||
ret1 = req1.cached_setup(setup, teardown, scope="function")
|
||||
assert l == ['setup']
|
||||
# artificial call of finalizer
|
||||
req1._pyfuncitem.session._setupstate._callfinalizers(item1)
|
||||
setupstate = req1._pyfuncitem.session._setupstate
|
||||
setupstate._callfinalizers(item1)
|
||||
assert l == ["setup", "teardown"]
|
||||
ret2 = req1.cached_setup(setup, teardown, scope="function")
|
||||
assert l == ["setup", "teardown", "setup"]
|
||||
req1._pyfuncitem.session._setupstate._callfinalizers(item1)
|
||||
setupstate._callfinalizers(item1)
|
||||
assert l == ["setup", "teardown", "setup", "teardown"]
|
||||
|
||||
def test_request_cached_setup_two_args(self, testdir):
|
||||
|
@ -1092,9 +1093,9 @@ class TestMetafuncFunctional:
|
|||
def pytest_generate_tests(metafunc):
|
||||
metafunc.addcall(param=metafunc)
|
||||
|
||||
def pytest_funcarg__metafunc(request):
|
||||
assert request._pyfuncitem._genid == "0"
|
||||
return request.param
|
||||
def pytest_funcarg__metafunc(item):
|
||||
assert item._genid == "0"
|
||||
return item.param
|
||||
|
||||
def test_function(metafunc, pytestconfig):
|
||||
assert metafunc.config == pytestconfig
|
||||
|
@ -1588,3 +1589,61 @@ def test_issue117_sessionscopeteardown(testdir):
|
|||
"*3/x*",
|
||||
"*ZeroDivisionError*",
|
||||
])
|
||||
|
||||
class TestRequestAPI:
|
||||
def test_addfinalizer_cachedsetup_getfuncargvalue(self, testdir):
|
||||
testdir.makeconftest("""
|
||||
l = []
|
||||
def pytest_runtest_setup(item):
|
||||
item.addfinalizer(lambda: l.append(1))
|
||||
l2 = item.getfuncargvalue("l")
|
||||
assert l2 is l
|
||||
item.cached_setup(lambda: l.append(2), lambda val: l.append(3),
|
||||
scope="function")
|
||||
def pytest_funcarg__l(request):
|
||||
return l
|
||||
""")
|
||||
testdir.makepyfile("""
|
||||
def test_hello():
|
||||
pass
|
||||
def test_hello2(l):
|
||||
assert l == [2, 3, 1, 2]
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines([
|
||||
"*2 passed*",
|
||||
])
|
||||
|
||||
def test_runtest_setup_sees_filled_funcargs(self, testdir):
|
||||
testdir.makeconftest("""
|
||||
def pytest_runtest_setup(item):
|
||||
assert item.funcargs is None
|
||||
""")
|
||||
testdir.makepyfile("""
|
||||
def pytest_funcarg__a(request):
|
||||
return 1
|
||||
def pytest_funcarg__b(request):
|
||||
return request.getfuncargvalue("a") + 1
|
||||
def test_hello(b):
|
||||
assert b == 2
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines([
|
||||
"*1 passed*",
|
||||
])
|
||||
|
||||
result = testdir.makeconftest("""
|
||||
import pytest
|
||||
@pytest.mark.trylast
|
||||
def pytest_runtest_setup(item):
|
||||
assert item.funcargs == {"a": 1, "b": 2}
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines([
|
||||
"*1 passed*",
|
||||
])
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ import py, pytest
|
|||
import os
|
||||
|
||||
from _pytest.tmpdir import pytest_funcarg__tmpdir, TempdirHandler
|
||||
from _pytest.python import FuncargRequest
|
||||
|
||||
def test_funcarg(testdir):
|
||||
item = testdir.getitem("""
|
||||
|
@ -11,12 +10,12 @@ def test_funcarg(testdir):
|
|||
metafunc.addcall(id='b')
|
||||
def test_func(tmpdir): pass
|
||||
""", 'test_func[a]')
|
||||
p = pytest_funcarg__tmpdir(FuncargRequest(item))
|
||||
p = pytest_funcarg__tmpdir(item)
|
||||
assert p.check()
|
||||
bn = p.basename.strip("0123456789")
|
||||
assert bn.endswith("test_func_a_")
|
||||
item.name = "qwe/\\abc"
|
||||
p = pytest_funcarg__tmpdir(FuncargRequest(item))
|
||||
p = pytest_funcarg__tmpdir(item)
|
||||
assert p.check()
|
||||
bn = p.basename.strip("0123456789")
|
||||
assert bn == "qwe__abc"
|
||||
|
|
Loading…
Reference in New Issue