drops special testcontext object in favour of "old" request object, simplifying communication and code for the 2.2-2.3 transition. also modify docs and examples.

This commit is contained in:
holger krekel 2012-09-17 16:36:10 +02:00
parent f6b555f5ad
commit 646c2c6001
10 changed files with 326 additions and 369 deletions

View File

@ -9,19 +9,9 @@ Changes between 2.2.4 and 2.3.0.dev
it is constructed correctly from the original current working dir. it is constructed correctly from the original current working dir.
- fix "python setup.py test" example to cause a proper "errno" return - fix "python setup.py test" example to cause a proper "errno" return
- fix issue165 - fix broken doc links and mention stackoverflow for FAQ - fix issue165 - fix broken doc links and mention stackoverflow for FAQ
- fix issue139 - merge FuncargRequest and Item API such that - fix issue139 - introduce @pytest.factory which allows direct scoping
funcarg-functionality is now directly available on the "item" and parametrization of funcarg factories. Introduce new @pytest.setup
object passed to the various pytest_runtest hooks. This allows more marker to allow the writing of setup functions which accept funcargs.
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 - catch unicode-issues when writing failure representations
to terminal to prevent the whole session from crashing to terminal to prevent the whole session from crashing
- fix xfail/skip confusion: a skip-mark or an imperative pytest.skip - fix xfail/skip confusion: a skip-mark or an imperative pytest.skip

View File

@ -912,31 +912,51 @@ class Function(FunctionMixin, pytest.Item):
def __hash__(self): def __hash__(self):
return hash((self.parent, self.name)) return hash((self.parent, self.name))
scope2props = dict(session=())
scope2props["module"] = ("fspath", "module")
scope2props["class"] = scope2props["module"] + ("cls",)
scope2props["instance"] = scope2props["class"] + ("instance", )
scope2props["function"] = scope2props["instance"] + ("function", "keywords")
def scopeproperty(name=None, doc=None):
def decoratescope(func):
scopename = name or func.__name__
def provide(self):
if func.__name__ in scope2props[self.scope]:
return func(self)
raise AttributeError("%s not available in %s-scoped context" % (
scopename, self.scope))
return property(provide, None, None, func.__doc__)
return decoratescope
def pytest_funcarg__request(__request__):
return __request__
#def pytest_funcarg__testcontext(__request__):
# return __request__
class FuncargRequest: class FuncargRequest:
""" (old-style) A request for function arguments from a test function. """ A request for function arguments from a test or setup function.
Note that there is an optional ``param`` attribute in case A request object gives access to attributes of the requesting
there was an invocation to metafunc.addcall(param=...). test context. It has an optional ``param`` attribute in case
If no such call was done in a ``pytest_generate_tests`` of parametrization.
hook, the attribute will not be present. Note that
as of pytest-2.3 you probably rather want to use the
testcontext object and mark your factory with a ``@pytest.factory``
marker.
""" """
def __init__(self, pyfuncitem): def __init__(self, pyfuncitem):
self._pyfuncitem = pyfuncitem self._pyfuncitem = pyfuncitem
if hasattr(pyfuncitem, '_requestparam'): if hasattr(pyfuncitem, '_requestparam'):
self.param = pyfuncitem._requestparam self.param = pyfuncitem._requestparam
#: Scope string, one of "function", "cls", "module", "session"
self.scope = "function"
self.getparent = pyfuncitem.getparent self.getparent = pyfuncitem.getparent
self._funcargs = self._pyfuncitem.funcargs.copy() self._funcargs = self._pyfuncitem.funcargs.copy()
self._funcargs["__request__"] = self
self._name2factory = {} self._name2factory = {}
self.funcargmanager = pyfuncitem.session.funcargmanager self.funcargmanager = pyfuncitem.session.funcargmanager
self._currentarg = None self._currentarg = None
self.funcargnames = getfuncargnames(self.function) self.funcargnames = getfuncargnames(self.function)
self.parentid = pyfuncitem.parent.nodeid self.parentid = pyfuncitem.parent.nodeid
self.scope = "function"
self._factorystack = [] self._factorystack = []
def _getfaclist(self, argname): def _getfaclist(self, argname):
@ -956,21 +976,42 @@ class FuncargRequest:
self.parentid) self.parentid)
return facdeflist return facdeflist
def raiseerror(self, msg):
""" raise a FuncargLookupError with the given message. """
raise self.funcargmanager.FuncargLookupError(self.function, msg)
@property @property
def config(self):
""" the pytest config object associated with this request. """
return self._pyfuncitem.config
@scopeproperty()
def function(self): def function(self):
""" function object of the test invocation. """ """ test function object if the request has a per-function scope. """
return self._pyfuncitem.obj return self._pyfuncitem.obj
@scopeproperty("class")
def cls(self):
""" class (can be None) where the test function was collected. """
clscol = self._pyfuncitem.getparent(pytest.Class)
if clscol:
return clscol.obj
@scopeproperty()
def instance(self):
""" instance (can be None) on which test function was collected. """
return py.builtin._getimself(self.function)
@scopeproperty()
def module(self):
""" python module object where the test function was collected. """
return self._pyfuncitem.getparent(pytest.Module).obj
@scopeproperty()
def fspath(self):
""" the file system path of the test module which collected this test. """
return self._pyfuncitem.fspath
@property @property
def keywords(self): def keywords(self):
""" keywords of the test function item. """ keywords of the test function item. """
.. versionadded:: 2.0
"""
return self._pyfuncitem.keywords return self._pyfuncitem.keywords
@property @property
@ -978,41 +1019,23 @@ class FuncargRequest:
""" pytest session object. """ """ pytest session object. """
return self._pyfuncitem.session return self._pyfuncitem.session
@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 addfinalizer(self, finalizer):
def config(self): """add finalizer/teardown function to be called after the
""" the pytest config object associated with this request. """ last test within the requesting test context finished
return self._pyfuncitem.config execution. """
self._addfinalizer(finalizer, scope=self.scope)
@property def _addfinalizer(self, finalizer, scope):
def fspath(self): if scope != "function" and hasattr(self, "param"):
""" the file system path of the test module which collected this test. """ # parametrized resources are sorted by param
return self._pyfuncitem.fspath # so we rather store finalizers per (argname, param)
colitem = (self._currentarg, self.param)
def _fillfuncargs(self): else:
if self.funcargnames: colitem = self._getscopeitem(scope)
assert not getattr(self._pyfuncitem, '_args', None), ( self._pyfuncitem.session._setupstate.addfinalizer(
"yielded functions cannot have funcargs") finalizer=finalizer, colitem=colitem)
while self.funcargnames:
argname = self.funcargnames.pop(0)
if argname not in self._pyfuncitem.funcargs:
self._pyfuncitem.funcargs[argname] = \
self.getfuncargvalue(argname)
def applymarker(self, marker): def applymarker(self, marker):
""" Apply a marker to a single test function invocation. """ Apply a marker to a single test function invocation.
@ -1026,11 +1049,33 @@ class FuncargRequest:
raise ValueError("%r is not a py.test.mark.* object") raise ValueError("%r is not a py.test.mark.* object")
self._pyfuncitem.keywords[marker.markname] = marker self._pyfuncitem.keywords[marker.markname] = marker
def raiseerror(self, msg):
""" raise a FuncargLookupError with the given message. """
raise self.funcargmanager.FuncargLookupError(self.function, msg)
def _fillfuncargs(self):
if self.funcargnames:
assert not getattr(self._pyfuncitem, '_args', None), (
"yielded functions cannot have funcargs")
while self.funcargnames:
argname = self.funcargnames.pop(0)
if argname not in self._pyfuncitem.funcargs:
self._pyfuncitem.funcargs[argname] = \
self.getfuncargvalue(argname)
def _callsetup(self):
self.funcargmanager.ensure_setupcalls(self)
def cached_setup(self, setup, teardown=None, scope="module", extrakey=None): def cached_setup(self, setup, teardown=None, scope="module", extrakey=None):
""" Return a testing resource managed by ``setup`` & """ (deprecated) Return a testing resource managed by ``setup`` &
``teardown`` calls. ``scope`` and ``extrakey`` determine when the ``teardown`` calls. ``scope`` and ``extrakey`` determine when the
``teardown`` function will be called so that subsequent calls to ``teardown`` function will be called so that subsequent calls to
``setup`` would recreate the resource. ``setup`` would recreate the resource. With pytest-2.3 you
do not need ``cached_setup()`` as you can directly declare a scope
on a funcarg factory and register a finalizer through
``request.addfinalizer()``.
:arg teardown: function receiving a previously setup resource. :arg teardown: function receiving a previously setup resource.
:arg setup: a no-argument function creating a resource. :arg setup: a no-argument function creating a resource.
@ -1061,16 +1106,19 @@ class FuncargRequest:
return val return val
def _callsetup(self):
self.funcargmanager.ensure_setupcalls(self)
def getfuncargvalue(self, argname): def getfuncargvalue(self, argname):
""" Retrieve a function argument by name for this test """ (deprecated) Retrieve a function argument by name for this test
function invocation. This allows one function argument factory function invocation. This allows one function argument factory
to call another function argument factory. If there are two to call another function argument factory. If there are two
funcarg factories for the same test function argument the first funcarg factories for the same test function argument the first
factory may use ``getfuncargvalue`` to call the second one and factory may use ``getfuncargvalue`` to call the second one and
do something additional with the resource. do something additional with the resource.
**Note**, however, that starting with
pytest-2.3 it is easier and better to directly state the needed
funcarg in the factory signature. This will also work seemlessly
with parametrization and the new resource setup optimizations.
""" """
try: try:
return self._funcargs[argname] return self._funcargs[argname]
@ -1091,11 +1139,6 @@ class FuncargRequest:
factory_kwargs = {} factory_kwargs = {}
def fillfactoryargs(): def fillfactoryargs():
for newname in newnames: for newname in newnames:
if newname == "testcontext":
val = TestContextResource(self)
elif newname == "request" and not factorydef.new:
val = self
else:
val = self.getfuncargvalue(newname) val = self.getfuncargvalue(newname)
factory_kwargs[newname] = val factory_kwargs[newname] = val
@ -1161,21 +1204,6 @@ class FuncargRequest:
return self._pyfuncitem.getparent(pytest.Module) return self._pyfuncitem.getparent(pytest.Module)
raise ValueError("unknown finalization scope %r" %(scope,)) 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=self.scope)
def _addfinalizer(self, finalizer, scope):
if scope != "function" and hasattr(self, "param"):
# parametrized resources are sorted by param
# so we rather store finalizers per (argname, param)
colitem = (self._currentarg, self.param)
else:
colitem = self._getscopeitem(scope)
self._pyfuncitem.session._setupstate.addfinalizer(
finalizer=finalizer, colitem=colitem)
def __repr__(self): def __repr__(self):
return "<FuncargRequest for %r>" %(self._pyfuncitem) return "<FuncargRequest for %r>" %(self._pyfuncitem)
@ -1396,17 +1424,13 @@ class FuncargManager:
if setupcall.active: if setupcall.active:
continue continue
request._factorystack.append(setupcall) request._factorystack.append(setupcall)
mp = monkeypatch()
try: try:
testcontext = TestContextSetup(request, setupcall) #mp.setattr(request, "_setupcall", setupcall, raising=False)
mp.setattr(request, "scope", setupcall.scope)
kwargs = {} kwargs = {}
for name in setupcall.funcargnames: for name in setupcall.funcargnames:
try:
kwargs[name] = request.getfuncargvalue(name) kwargs[name] = request.getfuncargvalue(name)
except FuncargLookupError:
if name == "testcontext":
kwargs[name] = testcontext
else:
raise
scope = setupcall.scope or "function" scope = setupcall.scope or "function"
scol = setupcall.scopeitem = request._getscopeitem(scope) scol = setupcall.scopeitem = request._getscopeitem(scope)
self.session._setupstate.addfinalizer(setupcall.finish, scol) self.session._setupstate.addfinalizer(setupcall.finish, scol)
@ -1414,6 +1438,7 @@ class FuncargManager:
self.addargfinalizer(setupcall.finish, argname) self.addargfinalizer(setupcall.finish, argname)
setupcall.execute(kwargs) setupcall.execute(kwargs)
finally: finally:
mp.undo()
request._factorystack.remove(setupcall) request._factorystack.remove(setupcall)
def addargfinalizer(self, finalizer, argname): def addargfinalizer(self, finalizer, argname):
@ -1427,69 +1452,6 @@ class FuncargManager:
except ValueError: except ValueError:
pass pass
scope2props = dict(session=())
scope2props["module"] = ("fspath", "module")
scope2props["class"] = scope2props["module"] + ("cls",)
scope2props["function"] = scope2props["class"] + ("function", "keywords")
def scopeprop(attr, name=None, doc=None):
if doc is None:
doc = ("%s of underlying test context, may not exist "
"if the testcontext has a higher scope" % (attr,))
name = name or attr
def get(self):
if name in scope2props[self.scope]:
return getattr(self._request, name)
raise AttributeError("%s not available in %s-scoped context" % (
name, self.scope))
return property(get, doc=doc)
def rprop(attr, doc=None):
if doc is None:
doc = "%s of underlying test context" % attr
return property(lambda x: getattr(x._request, attr), doc=doc)
class TestContext(object):
""" Basic objects of the current testing context. """
def __init__(self, request, scope):
self._request = request
self.scope = scope
# no getfuncargvalue(), cached_setup, applymarker helpers here
# on purpose
config = rprop("config", "pytest config object.")
session = rprop("session", "pytest session object.")
function = scopeprop("function")
module = scopeprop("module")
cls = scopeprop("class", "cls")
instance = scopeprop("instance")
fspath = scopeprop("fspath")
#keywords = scopeprop("keywords")
class TestContextSetup(TestContext):
def __init__(self, request, setupcall):
self._setupcall = setupcall
self._finalizers = []
super(TestContextSetup, self).__init__(request, setupcall.scope)
def addfinalizer(self, finalizer):
""" Add a finalizer to be called after the last test in the
test context executes. """
self._setupcall.addfinalizer(finalizer)
class TestContextResource(TestContext):
param = rprop("param")
def __init__(self, request):
super(TestContextResource, self).__init__(request, request.scope)
def addfinalizer(self, finalizer):
""" Add a finalizer to be called after the last test in the
test context executes. """
self._request.addfinalizer(finalizer)
class SetupCall: class SetupCall:
""" a container/helper for managing calls to setup functions. """ """ a container/helper for managing calls to setup functions. """

View File

@ -2,9 +2,9 @@
import pytest import pytest
@pytest.factory("session") @pytest.factory("session")
def setup(testcontext): def setup(request):
setup = CostlySetup() setup = CostlySetup()
testcontext.addfinalizer(setup.finalize) request.addfinalizer(setup.finalize)
return setup return setup
class CostlySetup: class CostlySetup:

View File

@ -6,13 +6,13 @@ import py, pytest
pythonlist = ['python2.4', 'python2.5', 'python2.6', 'python2.7', 'python2.8'] pythonlist = ['python2.4', 'python2.5', 'python2.6', 'python2.7', 'python2.8']
@pytest.factory(params=pythonlist) @pytest.factory(params=pythonlist)
def python1(testcontext, tmpdir): def python1(request, tmpdir):
picklefile = tmpdir.join("data.pickle") picklefile = tmpdir.join("data.pickle")
return Python(testcontext.param, picklefile) return Python(request.param, picklefile)
@pytest.factory(params=pythonlist) @pytest.factory(params=pythonlist)
def python2(testcontext, python1): def python2(request, python1):
return Python(testcontext.param, python1.picklefile) return Python(request.param, python1.picklefile)
class Python: class Python:
def __init__(self, version, picklefile): def __init__(self, version, picklefile):

View File

@ -66,15 +66,15 @@ Direct scoping of funcarg factories
Instead of calling cached_setup(), you can use the :ref:`@pytest.factory <@pytest.factory>` decorator and directly state the scope:: Instead of calling cached_setup(), you can use the :ref:`@pytest.factory <@pytest.factory>` decorator and directly state the scope::
@pytest.factory(scope="session") @pytest.factory(scope="session")
def db(testcontext): def db(request):
# factory will only be invoked once per session - # factory will only be invoked once per session -
db = DataBase() db = DataBase()
testcontext.addfinalizer(db.destroy) # destroy when session is finished request.addfinalizer(db.destroy) # destroy when session is finished
return db return db
This factory implementation does not need to call ``cached_setup()`` anymore This factory implementation does not need to call ``cached_setup()`` anymore
because it will only be invoked once per session. Moreover, the because it will only be invoked once per session. Moreover, the
``testcontext.addfinalizer()`` registers a finalizer according to the specified ``request.addfinalizer()`` registers a finalizer according to the specified
resource scope on which the factory function is operating. resource scope on which the factory function is operating.
@ -88,29 +88,29 @@ parametrization, i.e. calling a test multiple times with different value
sets. pytest-2.3 introduces a decorator for use on the factory itself:: sets. pytest-2.3 introduces a decorator for use on the factory itself::
@pytest.factory(params=["mysql", "pg"]) @pytest.factory(params=["mysql", "pg"])
def db(testcontext): def db(request):
... # use testcontext.param ... # use request.param
Here the factory will be invoked twice (with the respective "mysql" Here the factory will be invoked twice (with the respective "mysql"
and "pg" values set as ``testcontext.param`` attributes) and and all of and "pg" values set as ``request.param`` attributes) and and all of
the tests requiring "db" will run twice as well. The "mysql" and the tests requiring "db" will run twice as well. The "mysql" and
"pg" values will also be used for reporting the test-invocation variants. "pg" values will also be used for reporting the test-invocation variants.
This new way of parametrizing funcarg factories should in many cases This new way of parametrizing funcarg factories should in many cases
allow to re-use already written factories because effectively allow to re-use already written factories because effectively
``testcontext.param`` are already the parametrization attribute for test ``request.param`` are already the parametrization attribute for test
functions/classes were parametrized via functions/classes were parametrized via
:py:func:`~_pytest.python.Metafunc.parametrize(indirect=True)` calls. :py:func:`~_pytest.python.Metafunc.parametrize(indirect=True)` calls.
Of course it's perfectly fine to combine parametrization and scoping:: Of course it's perfectly fine to combine parametrization and scoping::
@pytest.factory(scope="session", params=["mysql", "pg"]) @pytest.factory(scope="session", params=["mysql", "pg"])
def db(testcontext): def db(request):
if testcontext.param == "mysql": if request.param == "mysql":
db = MySQL() db = MySQL()
elif testcontext.param == "pg": elif request.param == "pg":
db = PG() db = PG()
testcontext.addfinalizer(db.destroy) # destroy when session is finished request.addfinalizer(db.destroy) # destroy when session is finished
return db return db
This would execute all tests requiring the per-session "db" resource twice, This would execute all tests requiring the per-session "db" resource twice,
@ -126,7 +126,7 @@ denotes the name under which the resource can be accessed as a function
argument:: argument::
@pytest.factory() @pytest.factory()
def db(testcontext): def db(request):
... ...
The name under which the funcarg resource can be requested is ``db``. The name under which the funcarg resource can be requested is ``db``.

View File

@ -9,44 +9,45 @@ funcargs: resource injection and parametrization
.. note:: .. note::
pytest-2.3 introduces major refinements to the original funcarg pytest-2.3 introduces major refinements to the test setup and funcarg
mechanism introduced to pytest-2.0. While the old way mechanisms introduced to pytest-2.0. All pre-2.3 usages remain
remains fully supported, it is recommended to use the refined supported and several use cases, among them scoping and parametrization
mechanisms. See also the `compatibility notes`_ and the detailed of funcarg resources, are now easier to accomplish. For more background,
:ref:`reasoning for the new funcarg and setup functions <funcargcompare>`. see `compatibility notes`_ and the detailed :ref:`reasoning for the new
funcarg and setup functions <funcargcompare>`.
.. _`Dependency injection`: http://en.wikipedia.org/wiki/Dependency_injection .. _`Dependency injection`: http://en.wikipedia.org/wiki/Dependency_injection
Introduction Introduction
==================== ====================
py.test supports the injection of objects into test and setup functions pytest supports the injection of test resources into test and setup functions
and flexibly control their life cycle in relation to the overall test and flexibly control their life cycle in relation to the overall test
execution. Moreover, you can run a test function multiple times execution. Moreover, tests can get executed multiple times if you have
injecting different objects. different variants of test resources to test with.
The basic mechanism for injecting objects is called the *funcarg The basic mechanism for injecting objects is called the *funcarg
mechanism* because objects are **injected** when a test or setup mechanism* because objects are injected when a test or setup
function states it as an **argument**. The injected argument is **function** states it as an **argument**. The injected argument
created by a call to a registered **funcarg factory**. This approach is is created by a call to a registered **funcarg factory** for each argument
an example of `Dependency Injection`_ and helps to de-couple test code name. This mechanism is an example of `Dependency Injection`_
from the setup of required objects: at test writing time you do not need and helps to de-couple test code from the setup of required
to care for the details of where and how your required resources are objects: at test writing time you do not need to care for the details of
constructed, if they are shared on a per-class, module or session basis, where and how your required test resources are constructed, if they are
or if your test function is invoked multiple times with differently shared on a per-class, module or session basis, or if your test function
configured resource instances. is invoked multiple times with differently configured resource
instances.
Funcarg dependency injection allows to organise test resources
in a modular explicit way so that test functions state their needs
in their signature. pytest additionally offers powerful xunit-style
:ref:`setup functions <setup functions>` for the cases where you need
to create implicit test state that is not passed explicitely to test functions.
When a test function is invoked multiple times with different arguments we When a test function is invoked multiple times with different arguments we
speak of **parametrized testing**. This is useful if you want to test speak of **parametrized testing**. You can use it e. g. to repeatedly run test
e.g. against different database backends or want to write a parametrized functions against different database backends or to check that certain
test function, checking that certain inputs lead to certain outputs. inputs lead to certain outputs.
You can parametrize funcarg factories, parametrize test function
arguments or even implement your own parametrization scheme through a
plugin hook.
pytest additionally offers powerful xunit-style :ref:`setup functions <setup
functions>` for the cases where you need to create implicit test state
that is not passed explicitely to test functions.
Concretely, there are three main means of funcarg management: Concretely, there are three main means of funcarg management:
@ -54,7 +55,7 @@ Concretely, there are three main means of funcarg management:
their scoping and parametrization. Factories can themselves their scoping and parametrization. Factories can themselves
receive resources through their function arguments, easing receive resources through their function arguments, easing
the setup of `interdependent resources`_. Factories can use the setup of `interdependent resources`_. Factories can use
the special `testcontext`_ object to access details from where the special `request`_ object to access details from where
the factory or setup function is called and for registering finalizers. the factory or setup function is called and for registering finalizers.
* a `@pytest.mark.parametrize`_ marker for executing test functions * a `@pytest.mark.parametrize`_ marker for executing test functions
@ -103,8 +104,8 @@ factory function. Running the tests looks like this::
$ py.test test_simplefactory.py $ py.test test_simplefactory.py
=========================== test session starts ============================ =========================== test session starts ============================
platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev8 platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11
plugins: xdist, bugzilla, cache, oejskit, cli, pep8, cov plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
collecting ... collected 1 items collecting ... collected 1 items
test_simplefactory.py F test_simplefactory.py F
@ -119,7 +120,7 @@ factory function. Running the tests looks like this::
E assert 42 == 17 E assert 42 == 17
test_simplefactory.py:8: AssertionError test_simplefactory.py:8: AssertionError
========================= 1 failed in 0.02 seconds ========================= ========================= 1 failed in 0.01 seconds =========================
This shows that the test function was called with a ``myfuncarg`` This shows that the test function was called with a ``myfuncarg``
argument value of ``42`` and the assert fails as expected. Here is argument value of ``42`` and the assert fails as expected. Here is
@ -186,7 +187,7 @@ test session::
import smtplib import smtplib
@pytest.factory(scope="session") @pytest.factory(scope="session")
def smtp(testcontext): def smtp(request):
return smtplib.SMTP("merlinux.eu") return smtplib.SMTP("merlinux.eu")
The name of the factory is ``smtp`` (the factory function name) The name of the factory is ``smtp`` (the factory function name)
@ -214,7 +215,7 @@ inspect what is going on and can now run the tests::
================================= FAILURES ================================= ================================= FAILURES =================================
________________________________ test_ehlo _________________________________ ________________________________ test_ehlo _________________________________
smtp = <smtplib.SMTP instance at 0x25e7b48> smtp = <smtplib.SMTP instance at 0x31bce18>
def test_ehlo(smtp): def test_ehlo(smtp):
response = smtp.ehlo() response = smtp.ehlo()
@ -226,7 +227,7 @@ inspect what is going on and can now run the tests::
test_module.py:5: AssertionError test_module.py:5: AssertionError
________________________________ test_noop _________________________________ ________________________________ test_noop _________________________________
smtp = <smtplib.SMTP instance at 0x25e7b48> smtp = <smtplib.SMTP instance at 0x31bce18>
def test_noop(smtp): def test_noop(smtp):
response = smtp.noop() response = smtp.noop()
@ -235,7 +236,7 @@ inspect what is going on and can now run the tests::
E assert 0 E assert 0
test_module.py:10: AssertionError test_module.py:10: AssertionError
2 failed in 0.20 seconds 2 failed in 0.26 seconds
you see the two ``assert 0`` failing and can also see that you see the two ``assert 0`` failing and can also see that
the same (session-scoped) object was passed into the two test functions. the same (session-scoped) object was passed into the two test functions.
@ -247,7 +248,7 @@ Parametrizing a session-shared funcarg resource
Extending the previous example, we can flag the factory to create Extending the previous example, we can flag the factory to create
two ``smtp`` values which will cause all tests using it to two ``smtp`` values which will cause all tests using it to
run twice with two different values. The factory function gets run twice with two different values. The factory function gets
access to each parameter through the special `testcontext`_ object:: access to each parameter through the special `request`_ object::
# content of conftest.py # content of conftest.py
import pytest import pytest
@ -255,11 +256,11 @@ access to each parameter through the special `testcontext`_ object::
@pytest.factory(scope="session", @pytest.factory(scope="session",
params=["merlinux.eu", "mail.python.org"]) params=["merlinux.eu", "mail.python.org"])
def smtp(testcontext): def smtp(request):
return smtplib.SMTP(testcontext.param) return smtplib.SMTP(request.param)
The main change is the definition of a ``params`` list in the The main change is the definition of a ``params`` list in the
``factory``-marker and the ``testcontext.param`` access within the ``factory``-marker and the ``request.param`` access within the
factory function. No test function code needs to change. factory function. No test function code needs to change.
So let's just do another run:: So let's just do another run::
@ -269,7 +270,7 @@ So let's just do another run::
================================= FAILURES ================================= ================================= FAILURES =================================
__________________________ test_ehlo[merlinux.eu] __________________________ __________________________ test_ehlo[merlinux.eu] __________________________
smtp = <smtplib.SMTP instance at 0x2960050> smtp = <smtplib.SMTP instance at 0x28dc5a8>
def test_ehlo(smtp): def test_ehlo(smtp):
response = smtp.ehlo() response = smtp.ehlo()
@ -281,7 +282,7 @@ So let's just do another run::
test_module.py:5: AssertionError test_module.py:5: AssertionError
__________________________ test_noop[merlinux.eu] __________________________ __________________________ test_noop[merlinux.eu] __________________________
smtp = <smtplib.SMTP instance at 0x2960050> smtp = <smtplib.SMTP instance at 0x28dc5a8>
def test_noop(smtp): def test_noop(smtp):
response = smtp.noop() response = smtp.noop()
@ -292,7 +293,7 @@ So let's just do another run::
test_module.py:10: AssertionError test_module.py:10: AssertionError
________________________ test_ehlo[mail.python.org] ________________________ ________________________ test_ehlo[mail.python.org] ________________________
smtp = <smtplib.SMTP instance at 0x296a128> smtp = <smtplib.SMTP instance at 0x28e3e18>
def test_ehlo(smtp): def test_ehlo(smtp):
response = smtp.ehlo() response = smtp.ehlo()
@ -303,7 +304,7 @@ So let's just do another run::
test_module.py:4: AssertionError test_module.py:4: AssertionError
________________________ test_noop[mail.python.org] ________________________ ________________________ test_noop[mail.python.org] ________________________
smtp = <smtplib.SMTP instance at 0x296a128> smtp = <smtplib.SMTP instance at 0x28e3e18>
def test_noop(smtp): def test_noop(smtp):
response = smtp.noop() response = smtp.noop()
@ -312,7 +313,7 @@ So let's just do another run::
E assert 0 E assert 0
test_module.py:10: AssertionError test_module.py:10: AssertionError
4 failed in 6.00 seconds 4 failed in 6.17 seconds
We get four failures because we are running the two tests twice with We get four failures because we are running the two tests twice with
different ``smtp`` instantiations as defined on the factory. different ``smtp`` instantiations as defined on the factory.
@ -324,7 +325,7 @@ Adding a finalizer to the parametrized resource
Further extending the ``smtp`` example, we now want to properly Further extending the ``smtp`` example, we now want to properly
close a smtp server connection after the last test using it close a smtp server connection after the last test using it
has been run. We can do this by calling the ``testcontext.addfinalizer()`` has been run. We can do this by calling the ``request.addfinalizer()``
helper:: helper::
# content of conftest.py # content of conftest.py
@ -333,12 +334,12 @@ helper::
@pytest.factory(scope="session", @pytest.factory(scope="session",
params=["merlinux.eu", "mail.python.org"]) params=["merlinux.eu", "mail.python.org"])
def smtp(testcontext): def smtp(request):
smtp = smtplib.SMTP(testcontext.param) smtp = smtplib.SMTP(request.param)
def fin(): def fin():
print ("finalizing %s" % smtp) print ("finalizing %s" % smtp)
smtp.close() smtp.close()
testcontext.addfinalizer(fin) request.addfinalizer(fin)
return smtp return smtp
We also add a print call and then run py.test without the default We also add a print call and then run py.test without the default
@ -347,9 +348,9 @@ output capturing and disabled traceback reporting::
$ py.test -s -q --tb=no $ py.test -s -q --tb=no
collecting ... collected 4 items collecting ... collected 4 items
FFFF FFFF
4 failed in 5.62 seconds 4 failed in 6.40 seconds
finalizing <smtplib.SMTP instance at 0x2d60368> finalizing <smtplib.SMTP instance at 0x125d3b0>
finalizing <smtplib.SMTP instance at 0x2d68e60> finalizing <smtplib.SMTP instance at 0x1265b90>
We see that the two ``smtp`` instances are finalized appropriately. We see that the two ``smtp`` instances are finalized appropriately.
@ -360,8 +361,8 @@ You can also look at what tests pytest collects without running them::
$ py.test --collectonly $ py.test --collectonly
=========================== test session starts ============================ =========================== test session starts ============================
platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev8 platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11
plugins: xdist, bugzilla, cache, oejskit, cli, pep8, cov plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
collecting ... collected 4 items collecting ... collected 4 items
<Module 'test_module.py'> <Module 'test_module.py'>
<Function 'test_ehlo[merlinux.eu]'> <Function 'test_ehlo[merlinux.eu]'>
@ -369,7 +370,7 @@ You can also look at what tests pytest collects without running them::
<Function 'test_ehlo[mail.python.org]'> <Function 'test_ehlo[mail.python.org]'>
<Function 'test_noop[mail.python.org]'> <Function 'test_noop[mail.python.org]'>
============================= in 0.02 seconds ============================= ============================= in 0.01 seconds =============================
Note that pytest orders your test run by resource usage, minimizing Note that pytest orders your test run by resource usage, minimizing
the number of active resources at any given time. the number of active resources at any given time.
@ -404,15 +405,15 @@ the ``smtp`` resource by listing it as an input parameter. Let's run this::
$ py.test -v test_appsetup.py $ py.test -v test_appsetup.py
=========================== test session starts ============================ =========================== test session starts ============================
platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev8 -- /home/hpk/venv/1/bin/python platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11 -- /home/hpk/venv/1/bin/python
cachedir: /home/hpk/tmp/doc-exec-414/.cache cachedir: /home/hpk/tmp/doc-exec-423/.cache
plugins: xdist, bugzilla, cache, oejskit, cli, pep8, cov plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
collecting ... collected 2 items collecting ... collected 2 items
test_appsetup.py:12: test_exists[merlinux.eu] PASSED test_appsetup.py:12: test_exists[merlinux.eu] PASSED
test_appsetup.py:12: test_exists[mail.python.org] PASSED test_appsetup.py:12: test_exists[mail.python.org] PASSED
========================= 2 passed in 5.37 seconds ========================= ========================= 2 passed in 6.82 seconds =========================
Due to the parametrization of ``smtp`` the test will run twice with two Due to the parametrization of ``smtp`` the test will run twice with two
different ``App`` instances and respective smtp servers. There is no different ``App`` instances and respective smtp servers. There is no
@ -445,17 +446,17 @@ to show the flow of calls::
import pytest import pytest
@pytest.factory(scope="module", params=["mod1", "mod2"]) @pytest.factory(scope="module", params=["mod1", "mod2"])
def modarg(testcontext): def modarg(request):
param = testcontext.param param = request.param
print "create", param print "create", param
def fin(): def fin():
print "fin", param print "fin", param
testcontext.addfinalizer(fin) request.addfinalizer(fin)
return param return param
@pytest.factory(scope="function", params=[1,2]) @pytest.factory(scope="function", params=[1,2])
def otherarg(testcontext): def otherarg(request):
return testcontext.param return request.param
def test_0(otherarg): def test_0(otherarg):
print " test0", otherarg print " test0", otherarg
@ -468,9 +469,9 @@ Let's run the tests in verbose mode and with looking at the print-output::
$ py.test -v -s test_module.py $ py.test -v -s test_module.py
=========================== test session starts ============================ =========================== test session starts ============================
platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev8 -- /home/hpk/venv/1/bin/python platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11 -- /home/hpk/venv/1/bin/python
cachedir: /home/hpk/tmp/doc-exec-414/.cache cachedir: /home/hpk/tmp/doc-exec-423/.cache
plugins: xdist, bugzilla, cache, oejskit, cli, pep8, cov plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
collecting ... collected 8 items collecting ... collected 8 items
test_module.py:16: test_0[1] PASSED test_module.py:16: test_0[1] PASSED
@ -501,26 +502,29 @@ a re-ordering of test execution. The finalizer for the ``mod1`` parametrized
resource was executed before the ``mod2`` resource was setup. resource was executed before the ``mod2`` resource was setup.
.. currentmodule:: _pytest.python .. currentmodule:: _pytest.python
.. _`testcontext`: .. _`request`:
``testcontext``: interacting with test context ``request``: interacting with test invocation context
--------------------------------------------------- -------------------------------------------------------
The ``testcontext`` object may be used by `@pytest.factory`_ or The ``request`` object may be received by `@pytest.factory`_ or
:ref:`@pytest.setup <setup>` marked functions. It contains :ref:`@pytest.setup <setup>` marked functions and provides methods
information relating to the test context within which the marked
function executes. Moreover, you can call
``testcontext.addfinalizer(myfinalizer)`` in order to trigger a call to
``myfinalizer`` after the last test in the test context has executed.
If passed to a parametrized factory ``testcontext.param`` will contain a
parameter (one value out of the ``params`` list specified with the
`@pytest.factory`_ marker).
.. autoclass:: _pytest.python.TestContext() * to inspect attributes of the requesting test context, such as
``function``, ``cls``, ``module``, ``session`` and the pytest
``config`` object. A request object passed to a parametrized factory
will also carry a ``request.param`` object (A parametrized factory and
all of its dependent tests will be called with each of the factory-specified
``params``).
* to add finalizers/teardowns to be invoked when the last
test of the requesting test context executes
.. autoclass:: _pytest.python.FuncargRequest()
:members: :members:
.. _`test generators`: .. _`test generators`:
.. _`parametrizing-tests`: .. _`parametrizing-tests`:
.. _`parametrized test functions`: .. _`parametrized test functions`:
@ -566,7 +570,6 @@ two ``(input, output)`` arguments of ``test_eval`` function so the latter
will be run three times:: will be run three times::
$ py.test -q $ py.test -q
collecting ... collected 13 items collecting ... collected 13 items
....F........ ....F........
================================= FAILURES ================================= ================================= FAILURES =================================
@ -585,7 +588,7 @@ will be run three times::
E + where 54 = eval('6*9') E + where 54 = eval('6*9')
test_expectation.py:8: AssertionError test_expectation.py:8: AssertionError
1 failed, 12 passed in 5.76 seconds 1 failed, 12 passed in 6.41 seconds
As expected only one pair of input/output values fails the simple test function. As expected only one pair of input/output values fails the simple test function.
As usual you can see the ``input`` and ``output`` values in the traceback. As usual you can see the ``input`` and ``output`` values in the traceback.
@ -662,11 +665,11 @@ This means that we only run two tests if no option is passed::
$ py.test -q test_compute.py $ py.test -q test_compute.py
collecting ... collected 2 items collecting ... collected 2 items
.. ..
2 passed in 0.02 seconds 2 passed in 0.01 seconds
And we run five tests if we add the ``--all`` option:: And we run five tests if we add the ``--all`` option::
$ py.test -q --all $ py.test -q --all test_compute.py
collecting ... collected 5 items collecting ... collected 5 items
....F ....F
================================= FAILURES ================================= ================================= FAILURES =================================
@ -679,7 +682,7 @@ And we run five tests if we add the ``--all`` option::
E assert 4 < 4 E assert 4 < 4
test_compute.py:3: AssertionError test_compute.py:3: AssertionError
1 failed, 4 passed in 0.03 seconds 1 failed, 4 passed in 0.02 seconds
As expected when running the full range of ``param1`` values As expected when running the full range of ``param1`` values
we'll get an error on the last one. we'll get an error on the last one.
@ -748,13 +751,4 @@ the mechanism was extended and refined:
use remains fully supported and existing code using it should run use remains fully supported and existing code using it should run
unmodified. unmodified.
.. _request:
The request object passed to old-style factories
-----------------------------------------------------------------
Old-style funcarg factory definitions can receive a :py:class:`~_pytest.python.FuncargRequest` 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.

View File

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

View File

@ -33,7 +33,7 @@ you can do with the old-style and much more. Specifically setup functions:
- fully interoperate with parametrized resources, - fully interoperate with parametrized resources,
- can be defined in a plugin or :ref:`conftest.py <conftest.py>` file and get called - can be defined in a plugin or :ref:`conftest.py <conftest.py>` file and get called
on a per-session, per-module, per-class or per-function basis, on a per-session, per-module, per-class or per-function basis,
- can access the :ref:`testcontext <testcontext>` for which the setup is called, - can access the :ref:`request <request>` for which the setup is called,
- can precisely control teardown by registering one or multiple - can precisely control teardown by registering one or multiple
teardown functions as soon as they have performed some actions teardown functions as soon as they have performed some actions
which need undoing, eliminating the no need for a separate which need undoing, eliminating the no need for a separate
@ -140,8 +140,8 @@ a per-module setup function. We use a :ref:`resource factory
return GlobalResource() return GlobalResource()
@pytest.setup(scope="module") @pytest.setup(scope="module")
def setresource(testcontext, globresource): def setresource(request, globresource):
testcontext.module.globresource = globresource request.module.globresource = globresource
Now any test module can access ``globresource`` as a module global:: Now any test module can access ``globresource`` as a module global::
@ -178,16 +178,16 @@ factory and also add a finalizer::
self.param = param self.param = param
@pytest.factory(scope="session", params=[1,2]) @pytest.factory(scope="session", params=[1,2])
def globresource(testcontext): def globresource(request):
g = GlobalResource(testcontext.param) g = GlobalResource(request.param)
def fin(): def fin():
print "finalizing", g print "finalizing", g
testcontext.addfinalizer(fin) request.addfinalizer(fin)
return g return g
@pytest.setup(scope="module") @pytest.setup(scope="module")
def setresource(testcontext, globresource): def setresource(request, globresource):
testcontext.module.globresource = globresource request.module.globresource = globresource
And then re-run our test module:: And then re-run our test module::

View File

@ -1590,8 +1590,8 @@ class TestRequestAPI:
result = testdir.makeconftest(""" result = testdir.makeconftest("""
import pytest import pytest
@pytest.setup() @pytest.setup()
def mysetup(testcontext): def mysetup(request):
testcontext.uses_funcarg("db") request.uses_funcarg("db")
""") """)
result = testdir.runpytest() result = testdir.runpytest()
assert result.ret == 0 assert result.ret == 0
@ -1647,9 +1647,9 @@ class TestFuncargFactory:
import pytest import pytest
l = [] l = []
@pytest.factory(params=[1,2]) @pytest.factory(params=[1,2])
def arg1(testcontext): def arg1(request):
l.append(1) l.append(1)
return testcontext.param return request.param
@pytest.factory() @pytest.factory()
def arg2(arg1): def arg2(arg1):
@ -1758,7 +1758,7 @@ class TestSetupDiscovery:
testdir.makeconftest(""" testdir.makeconftest("""
import pytest import pytest
@pytest.setup() @pytest.setup()
def perfunction(testcontext, tmpdir): def perfunction(request, tmpdir):
pass pass
@pytest.factory() @pytest.factory()
@ -1782,10 +1782,10 @@ class TestSetupDiscovery:
setupcalls, allnames = fm.getsetuplist(item.nodeid) setupcalls, allnames = fm.getsetuplist(item.nodeid)
assert len(setupcalls) == 2 assert len(setupcalls) == 2
assert setupcalls[0].func.__name__ == "perfunction" assert setupcalls[0].func.__name__ == "perfunction"
assert "testcontext" in setupcalls[0].funcargnames assert "request" in setupcalls[0].funcargnames
assert "tmpdir" in setupcalls[0].funcargnames assert "tmpdir" in setupcalls[0].funcargnames
assert setupcalls[1].func.__name__ == "perfunction2" assert setupcalls[1].func.__name__ == "perfunction2"
assert "testcontext" not in setupcalls[1].funcargnames assert "request" not in setupcalls[1].funcargnames
assert "arg1" in setupcalls[1].funcargnames assert "arg1" in setupcalls[1].funcargnames
assert "tmpdir" not in setupcalls[1].funcargnames assert "tmpdir" not in setupcalls[1].funcargnames
#assert "tmpdir" in setupcalls[1].depfuncargs #assert "tmpdir" in setupcalls[1].depfuncargs
@ -1842,8 +1842,8 @@ class TestSetupManagement:
import pytest import pytest
l = [] l = []
@pytest.factory(params=[1,2]) @pytest.factory(params=[1,2])
def arg(testcontext): def arg(request):
return testcontext.param return request.param
@pytest.setup() @pytest.setup()
def something(arg): def something(arg):
@ -1868,12 +1868,12 @@ class TestSetupManagement:
l = [] l = []
@pytest.factory(scope="session", params=[1,2]) @pytest.factory(scope="session", params=[1,2])
def arg(testcontext): def arg(request):
return testcontext.param return request.param
@pytest.setup(scope="function") @pytest.setup(scope="function")
def append(testcontext, arg): def append(request, arg):
if testcontext.function.__name__ == "test_some": if request.function.__name__ == "test_some":
l.append(arg) l.append(arg)
def test_some(): def test_some():
@ -1894,18 +1894,18 @@ class TestSetupManagement:
l = [] l = []
@pytest.factory(scope="function", params=[1,2]) @pytest.factory(scope="function", params=[1,2])
def farg(testcontext): def farg(request):
return testcontext.param return request.param
@pytest.factory(scope="class", params=list("ab")) @pytest.factory(scope="class", params=list("ab"))
def carg(testcontext): def carg(request):
return testcontext.param return request.param
@pytest.setup(scope="class") @pytest.setup(scope="function")
def append(testcontext, farg, carg): def append(request, farg, carg):
def fin(): def fin():
l.append("fin_%s%s" % (carg, farg)) l.append("fin_%s%s" % (carg, farg))
testcontext.addfinalizer(fin) request.addfinalizer(fin)
""") """)
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest
@ -1950,8 +1950,8 @@ class TestFuncargMarker:
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest
@pytest.factory(params=["a", "b", "c"]) @pytest.factory(params=["a", "b", "c"])
def arg(testcontext): def arg(request):
return testcontext.param return request.param
l = [] l = []
def test_param(arg): def test_param(arg):
l.append(arg) l.append(arg)
@ -2011,10 +2011,10 @@ class TestFuncargMarker:
finalized = [] finalized = []
created = [] created = []
@pytest.factory(scope="module") @pytest.factory(scope="module")
def arg(testcontext): def arg(request):
created.append(1) created.append(1)
assert testcontext.scope == "module" assert request.scope == "module"
testcontext.addfinalizer(lambda: finalized.append(1)) request.addfinalizer(lambda: finalized.append(1))
def pytest_funcarg__created(request): def pytest_funcarg__created(request):
return len(created) return len(created)
def pytest_funcarg__finalized(request): def pytest_funcarg__finalized(request):
@ -2050,14 +2050,14 @@ class TestFuncargMarker:
finalized = [] finalized = []
created = [] created = []
@pytest.factory(scope="function") @pytest.factory(scope="function")
def arg(testcontext): def arg(request):
pass pass
""") """)
testdir.makepyfile( testdir.makepyfile(
test_mod1=""" test_mod1="""
import pytest import pytest
@pytest.factory(scope="session") @pytest.factory(scope="session")
def arg(testcontext): def arg(request):
%s %s
def test_1(arg): def test_1(arg):
pass pass
@ -2091,8 +2091,8 @@ class TestFuncargMarker:
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest
@pytest.factory(scope="module", params=["a", "b", "c"]) @pytest.factory(scope="module", params=["a", "b", "c"])
def arg(testcontext): def arg(request):
return testcontext.param return request.param
l = [] l = []
def test_param(arg): def test_param(arg):
l.append(arg) l.append(arg)
@ -2109,7 +2109,7 @@ class TestFuncargMarker:
testdir.makeconftest(""" testdir.makeconftest("""
import pytest import pytest
@pytest.factory(scope="function") @pytest.factory(scope="function")
def arg(testcontext): def arg(request):
pass pass
""") """)
testdir.makepyfile(""" testdir.makepyfile("""
@ -2131,8 +2131,8 @@ class TestFuncargMarker:
import pytest import pytest
@pytest.factory(scope="module", params=[1, 2]) @pytest.factory(scope="module", params=[1, 2])
def arg(testcontext): def arg(request):
return testcontext.param return request.param
l = [] l = []
def test_1(arg): def test_1(arg):
@ -2198,18 +2198,18 @@ class TestFuncargMarker:
l = [] l = []
@pytest.factory(scope="function", params=[1,2]) @pytest.factory(scope="function", params=[1,2])
def farg(testcontext): def farg(request):
return testcontext.param return request.param
@pytest.factory(scope="class", params=list("ab")) @pytest.factory(scope="class", params=list("ab"))
def carg(testcontext): def carg(request):
return testcontext.param return request.param
@pytest.setup(scope="class") @pytest.setup(scope="function")
def append(testcontext, farg, carg): def append(request, farg, carg):
def fin(): def fin():
l.append("fin_%s%s" % (carg, farg)) l.append("fin_%s%s" % (carg, farg))
testcontext.addfinalizer(fin) request.addfinalizer(fin)
""") """)
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest
@ -2244,18 +2244,18 @@ class TestFuncargMarker:
import pytest import pytest
@pytest.factory(scope="function", params=[1, 2]) @pytest.factory(scope="function", params=[1, 2])
def arg(testcontext): def arg(request):
param = testcontext.param param = request.param
testcontext.addfinalizer(lambda: l.append("fin:%s" % param)) request.addfinalizer(lambda: l.append("fin:%s" % param))
l.append("create:%s" % param) l.append("create:%s" % param)
return testcontext.param return request.param
@pytest.factory(scope="module", params=["mod1", "mod2"]) @pytest.factory(scope="module", params=["mod1", "mod2"])
def modarg(testcontext): def modarg(request):
param = testcontext.param param = request.param
testcontext.addfinalizer(lambda: l.append("fin:%s" % param)) request.addfinalizer(lambda: l.append("fin:%s" % param))
l.append("create:%s" % param) l.append("create:%s" % param)
return testcontext.param return request.param
l = [] l = []
def test_1(arg): def test_1(arg):
@ -2288,11 +2288,11 @@ class TestFuncargMarker:
import pytest import pytest
@pytest.factory(scope="module", params=[1, 2]) @pytest.factory(scope="module", params=[1, 2])
def arg(testcontext): def arg(request):
testcontext.config.l = l # to access from outer request.config.l = l # to access from outer
x = testcontext.param x = request.param
testcontext.addfinalizer(lambda: l.append("fin%s" % x)) request.addfinalizer(lambda: l.append("fin%s" % x))
return testcontext.param return request.param
l = [] l = []
def test_1(arg): def test_1(arg):
@ -2317,10 +2317,10 @@ class TestFuncargMarker:
import pytest import pytest
@pytest.factory(scope="function", params=[1, 2]) @pytest.factory(scope="function", params=[1, 2])
def arg(testcontext): def arg(request):
x = testcontext.param x = request.param
testcontext.addfinalizer(lambda: l.append("fin%s" % x)) request.addfinalizer(lambda: l.append("fin%s" % x))
return testcontext.param return request.param
l = [] l = []
def test_1(arg): def test_1(arg):
@ -2339,12 +2339,12 @@ class TestFuncargMarker:
import pytest import pytest
@pytest.factory(scope="module", params=[1, 2]) @pytest.factory(scope="module", params=[1, 2])
def arg(testcontext): def arg(request):
return testcontext.param return request.param
@pytest.setup(scope="module") @pytest.setup(scope="module")
def mysetup(testcontext, arg): def mysetup(request, arg):
testcontext.addfinalizer(lambda: l.append("fin%s" % arg)) request.addfinalizer(lambda: l.append("fin%s" % arg))
l.append("setup%s" % arg) l.append("setup%s" % arg)
l = [] l = []
@ -2376,32 +2376,32 @@ class TestTestContextScopeAccess:
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest
@pytest.setup(scope=%r) @pytest.setup(scope=%r)
def myscoped(testcontext): def myscoped(request):
for x in %r: for x in %r:
assert hasattr(testcontext, x) assert hasattr(request, x)
for x in %r: for x in %r:
pytest.raises(AttributeError, lambda: pytest.raises(AttributeError, lambda:
getattr(testcontext, x)) getattr(request, x))
assert testcontext.session assert request.session
assert testcontext.config assert request.config
def test_func(): def test_func():
pass pass
""" %(scope, ok.split(), error.split())) """ %(scope, ok.split(), error.split()))
reprec = testdir.inline_run() reprec = testdir.inline_run("-l")
reprec.assertoutcome(passed=1) reprec.assertoutcome(passed=1)
def test_funcarg(self, testdir, scope, ok, error): def test_funcarg(self, testdir, scope, ok, error):
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest
@pytest.factory(scope=%r) @pytest.factory(scope=%r)
def arg(testcontext): def arg(request):
for x in %r: for x in %r:
assert hasattr(testcontext, x) assert hasattr(request, x)
for x in %r: for x in %r:
pytest.raises(AttributeError, lambda: pytest.raises(AttributeError, lambda:
getattr(testcontext, x)) getattr(request, x))
assert testcontext.session assert request.session
assert testcontext.config assert request.config
def test_func(arg): def test_func(arg):
pass pass
""" %(scope, ok.split(), error.split())) """ %(scope, ok.split(), error.split()))
@ -2414,7 +2414,7 @@ class TestErrors:
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest
@pytest.factory() @pytest.factory()
def gen(request): def gen(qwe123):
return 1 return 1
def test_something(gen): def test_something(gen):
pass pass
@ -2422,8 +2422,8 @@ class TestErrors:
result = testdir.runpytest() result = testdir.runpytest()
assert result.ret != 0 assert result.ret != 0
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
"*def gen(request):*", "*def gen(qwe123):*",
"*no factory*request*", "*no factory*qwe123*",
"*1 error*", "*1 error*",
]) ])
@ -2431,7 +2431,7 @@ class TestErrors:
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest
@pytest.setup() @pytest.setup()
def gen(request): def gen(qwe123):
return 1 return 1
def test_something(): def test_something():
pass pass
@ -2439,14 +2439,14 @@ class TestErrors:
result = testdir.runpytest() result = testdir.runpytest()
assert result.ret != 0 assert result.ret != 0
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
"*def gen(request):*", "*def gen(qwe123):*",
"*no factory*request*", "*no factory*qwe123*",
"*1 error*", "*1 error*",
]) ])
class TestTestContextVarious: class TestTestContextVarious:
def test_newstyle_no_request(self, testdir): def test_newstyle_with_request(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest
@pytest.factory() @pytest.factory()
@ -2455,21 +2455,19 @@ class TestTestContextVarious:
def test_1(arg): def test_1(arg):
pass pass
""") """)
result = testdir.runpytest() reprec = testdir.inline_run()
result.stdout.fnmatch_lines([ reprec.assertoutcome(passed=1)
"*no factory found*request*",
])
def test_setupcontext_no_param(self, testdir): def test_setupcontext_no_param(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest
@pytest.factory(params=[1,2]) @pytest.factory(params=[1,2])
def arg(testcontext): def arg(request):
return testcontext.param return request.param
@pytest.setup() @pytest.setup()
def mysetup(testcontext, arg): def mysetup(request, arg):
assert not hasattr(testcontext, "param") assert not hasattr(request, "param")
def test_1(arg): def test_1(arg):
assert arg in (1,2) assert arg in (1,2)
""") """)
@ -2505,3 +2503,16 @@ def test_setupdecorator_and_xunit(testdir):
""") """)
reprec = testdir.inline_run() reprec = testdir.inline_run()
reprec.assertoutcome(passed=3) reprec.assertoutcome(passed=3)
def test_request_can_be_overridden(testdir):
testdir.makepyfile("""
import pytest
@pytest.factory()
def request(request):
request.a = 1
return request
def test_request(request):
assert request.a == 1
""")
reprec = testdir.inline_run()
reprec.assertoutcome(passed=1)

View File

@ -62,6 +62,9 @@ commands=
[testenv:py32] [testenv:py32]
deps=py>=1.4.0 deps=py>=1.4.0
[testenv:py33]
deps=py>=1.4.0
[testenv:jython] [testenv:jython]
changedir=testing changedir=testing
commands= commands=