[py-svn] commit/pytest: 2 new changesets

Bitbucket commits-noreply at bitbucket.org
Mon Sep 17 17:32:36 CEST 2012


2 new commits in pytest:


https://bitbucket.org/hpk42/pytest/changeset/6a73adc3a5f0/
changeset:   6a73adc3a5f0
user:        hpk42
date:        2012-09-17 16:36:10
summary:     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.
affected #:  10 files

diff -r 9b4c51b4a2a02e9a3d9a33a1ec35aa3cab942e63 -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -9,19 +9,9 @@
   it is constructed correctly from the original current working dir.
 - fix "python setup.py test" example to cause a proper "errno" return
 - fix issue165 - fix broken doc links and mention stackoverflow for FAQ
-- fix issue139 - merge FuncargRequest and Item API such that 
-  funcarg-functionality is now directly 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.
+- fix issue139 - introduce @pytest.factory which allows direct scoping
+  and parametrization of funcarg factories.  Introduce new @pytest.setup
+  marker to allow the writing of setup functions which accept funcargs.
 - 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


diff -r 9b4c51b4a2a02e9a3d9a33a1ec35aa3cab942e63 -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 _pytest/python.py
--- a/_pytest/python.py
+++ b/_pytest/python.py
@@ -912,31 +912,51 @@
     def __hash__(self):
         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:
-    """ (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
-        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.  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.
+    A request object gives access to attributes of the requesting
+    test context.  It has an optional ``param`` attribute in case
+    of parametrization.
     """
 
     def __init__(self, pyfuncitem):
         self._pyfuncitem = pyfuncitem
         if hasattr(pyfuncitem, '_requestparam'):
             self.param = pyfuncitem._requestparam
+        #: Scope string, one of "function", "cls", "module", "session"
+        self.scope = "function"
         self.getparent = pyfuncitem.getparent
         self._funcargs  = self._pyfuncitem.funcargs.copy()
+        self._funcargs["__request__"] = self
         self._name2factory = {}
         self.funcargmanager = pyfuncitem.session.funcargmanager
         self._currentarg = None
         self.funcargnames = getfuncargnames(self.function)
         self.parentid = pyfuncitem.parent.nodeid
-        self.scope = "function"
         self._factorystack = []
 
     def _getfaclist(self, argname):
@@ -956,21 +976,42 @@
                                                    self.parentid)
         return facdeflist
 
-    def raiseerror(self, msg):
-        """ raise a FuncargLookupError with the given message. """
-        raise self.funcargmanager.FuncargLookupError(self.function, msg)
+    @property
+    def config(self):
+        """ the pytest config object associated with this request. """
+        return self._pyfuncitem.config
 
-    @property
+
+    @scopeproperty()
     def function(self):
-        """ function object of the test invocation. """
+        """ test function object if the request has a per-function scope. """
         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
     def keywords(self):
-        """ keywords of the test function item.
-
-        .. versionadded:: 2.0
-        """
+        """ keywords of the test function item.  """
         return self._pyfuncitem.keywords
 
     @property
@@ -978,41 +1019,23 @@
         """ pytest session object. """
         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 config(self):
-        """ the pytest config object associated with this request. """
-        return self._pyfuncitem.config
+    def addfinalizer(self, finalizer):
+        """add finalizer/teardown function to be called after the
+        last test within the requesting test context finished
+        execution. """
+        self._addfinalizer(finalizer, scope=self.scope)
 
-    @property
-    def fspath(self):
-        """ the file system path of the test module which collected this test. """
-        return self._pyfuncitem.fspath
-
-    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 _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 applymarker(self, marker):
         """ Apply a marker to a single test function invocation.
@@ -1026,11 +1049,33 @@
             raise ValueError("%r is not a py.test.mark.* object")
         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):
-        """ Return a testing resource managed by ``setup`` &
+        """ (deprecated) 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.
+        ``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 setup: a no-argument function creating a resource.
@@ -1061,16 +1106,19 @@
         return val
 
 
-    def _callsetup(self):
-        self.funcargmanager.ensure_setupcalls(self)
 
     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
         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.
+
+        **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:
             return self._funcargs[argname]
@@ -1091,12 +1139,7 @@
         factory_kwargs = {}
         def fillfactoryargs():
             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
 
         node = self._pyfuncitem
@@ -1161,21 +1204,6 @@
             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=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):
         return "<FuncargRequest for %r>" %(self._pyfuncitem)
 
@@ -1396,17 +1424,13 @@
             if setupcall.active:
                 continue
             request._factorystack.append(setupcall)
+            mp = monkeypatch()
             try:
-                testcontext = TestContextSetup(request, setupcall)
+                #mp.setattr(request, "_setupcall", setupcall, raising=False)
+                mp.setattr(request, "scope", setupcall.scope)
                 kwargs = {}
                 for name in setupcall.funcargnames:
-                    try:
-                        kwargs[name] = request.getfuncargvalue(name)
-                    except FuncargLookupError:
-                        if name == "testcontext":
-                            kwargs[name] = testcontext
-                        else:
-                            raise
+                    kwargs[name] = request.getfuncargvalue(name)
                 scope = setupcall.scope or "function"
                 scol = setupcall.scopeitem = request._getscopeitem(scope)
                 self.session._setupstate.addfinalizer(setupcall.finish, scol)
@@ -1414,6 +1438,7 @@
                     self.addargfinalizer(setupcall.finish, argname)
                 setupcall.execute(kwargs)
             finally:
+                mp.undo()
                 request._factorystack.remove(setupcall)
 
     def addargfinalizer(self, finalizer, argname):
@@ -1427,69 +1452,6 @@
             except ValueError:
                 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:
     """ a container/helper for managing calls to setup functions. """


diff -r 9b4c51b4a2a02e9a3d9a33a1ec35aa3cab942e63 -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 doc/en/example/costlysetup/conftest.py
--- a/doc/en/example/costlysetup/conftest.py
+++ b/doc/en/example/costlysetup/conftest.py
@@ -2,9 +2,9 @@
 import pytest
 
 @pytest.factory("session")
-def setup(testcontext):
+def setup(request):
     setup = CostlySetup()
-    testcontext.addfinalizer(setup.finalize)
+    request.addfinalizer(setup.finalize)
     return setup
 
 class CostlySetup:


diff -r 9b4c51b4a2a02e9a3d9a33a1ec35aa3cab942e63 -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 doc/en/example/multipython.py
--- a/doc/en/example/multipython.py
+++ b/doc/en/example/multipython.py
@@ -6,13 +6,13 @@
 
 pythonlist = ['python2.4', 'python2.5', 'python2.6', 'python2.7', 'python2.8']
 @pytest.factory(params=pythonlist)
-def python1(testcontext, tmpdir):
+def python1(request, tmpdir):
     picklefile = tmpdir.join("data.pickle")
-    return Python(testcontext.param, picklefile)
+    return Python(request.param, picklefile)
 
 @pytest.factory(params=pythonlist)
-def python2(testcontext, python1):
-    return Python(testcontext.param, python1.picklefile)
+def python2(request, python1):
+    return Python(request.param, python1.picklefile)
 
 class Python:
     def __init__(self, version, picklefile):


diff -r 9b4c51b4a2a02e9a3d9a33a1ec35aa3cab942e63 -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 doc/en/funcarg_compare.txt
--- a/doc/en/funcarg_compare.txt
+++ b/doc/en/funcarg_compare.txt
@@ -66,15 +66,15 @@
 Instead of calling cached_setup(), you can use the :ref:`@pytest.factory <@pytest.factory>` decorator and directly state the scope::
 
     @pytest.factory(scope="session")
-    def db(testcontext):
+    def db(request):
         # factory will only be invoked once per session - 
         db = DataBase()
-        testcontext.addfinalizer(db.destroy)  # destroy when session is finished
+        request.addfinalizer(db.destroy)  # destroy when session is finished
         return db
 
 This factory implementation does not need to call ``cached_setup()`` anymore
 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.
 
 
@@ -88,29 +88,29 @@
 sets.  pytest-2.3 introduces a decorator for use on the factory itself::
 
     @pytest.factory(params=["mysql", "pg"])
-    def db(testcontext):
-        ... # use testcontext.param
+    def db(request):
+        ... # use request.param
 
 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 
 "pg" values will also be used for reporting the test-invocation variants.
 
 This new way of parametrizing funcarg factories should in many cases
 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
 :py:func:`~_pytest.python.Metafunc.parametrize(indirect=True)` calls.
 
 Of course it's perfectly fine to combine parametrization and scoping::
 
     @pytest.factory(scope="session", params=["mysql", "pg"])
-    def db(testcontext):
-        if testcontext.param == "mysql":
+    def db(request):
+        if request.param == "mysql":
             db = MySQL()
-        elif testcontext.param == "pg":
+        elif request.param == "pg":
             db = PG()
-        testcontext.addfinalizer(db.destroy)  # destroy when session is finished
+        request.addfinalizer(db.destroy)  # destroy when session is finished
         return db
 
 This would execute all tests requiring the per-session "db" resource twice,
@@ -126,7 +126,7 @@
 argument::
 
     @pytest.factory()
-    def db(testcontext):
+    def db(request):
         ...
 
 The name under which the funcarg resource can be requested is ``db``.


diff -r 9b4c51b4a2a02e9a3d9a33a1ec35aa3cab942e63 -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 doc/en/funcargs.txt
--- a/doc/en/funcargs.txt
+++ b/doc/en/funcargs.txt
@@ -9,44 +9,45 @@
 
 .. note::
 
-    pytest-2.3 introduces major refinements to the original funcarg
-    mechanism introduced to pytest-2.0.  While the old way
-    remains fully supported, it is recommended to use the refined
-    mechanisms.  See also the `compatibility notes`_ and the detailed
-    :ref:`reasoning for the new funcarg and setup functions <funcargcompare>`.
+    pytest-2.3 introduces major refinements to the test setup and funcarg
+    mechanisms introduced to pytest-2.0.  All pre-2.3 usages remain
+    supported and several use cases, among them scoping and parametrization
+    of funcarg resources, are now easier to accomplish.  For more background, 
+    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
 
 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
-execution.  Moreover, you can run a test function multiple times
-injecting different objects.
+execution.  Moreover, tests can get executed multiple times if you have
+different variants of test resources to test with.  
 
 The basic mechanism for injecting objects is called the *funcarg
-mechanism* because objects are **injected** when a test or setup
-function states it as an **argument**.  The injected argument is
-created by a call to a registered **funcarg factory**.  This approach is
-an example of `Dependency Injection`_ and helps to de-couple test code
-from the setup of required objects: at test writing time you do not need
-to care for the details of where and how your required resources are
-constructed, if they are shared on a per-class, module or session basis,
-or if your test function is invoked multiple times with differently
-configured resource instances.
+mechanism* because objects are injected when a test or setup
+**function** states it as an **argument**. The injected argument 
+is created by a call to a registered **funcarg factory** for each argument
+name.  This mechanism is an example of `Dependency Injection`_ 
+and helps to de-couple test code from the setup of required
+objects: at test writing time you do not need to care for the details of
+where and how your required test resources are constructed, if they are
+shared on a per-class, module or session basis, or if your test function
+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
-speak of **parametrized testing**.  This is useful if you want to test
-e.g. against different database backends or want to write a parametrized
-test function, checking that certain 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.
+speak of **parametrized testing**.  You can use it e. g. to repeatedly run test
+functions against different database backends or to check that certain
+inputs lead to certain outputs.
 
 Concretely, there are three main means of funcarg management:
 
@@ -54,7 +55,7 @@
   their scoping and parametrization.   Factories can themselves 
   receive resources through their function arguments, easing
   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.
 
 * a `@pytest.mark.parametrize`_ marker for executing test functions
@@ -103,8 +104,8 @@
 
     $ py.test test_simplefactory.py
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev8
-    plugins: xdist, bugzilla, cache, oejskit, cli, pep8, cov
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11
+    plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
     collecting ... collected 1 items
     
     test_simplefactory.py F
@@ -119,7 +120,7 @@
     E       assert 42 == 17
     
     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``
 argument value of ``42`` and the assert fails as expected.  Here is 
@@ -186,7 +187,7 @@
     import smtplib
 
     @pytest.factory(scope="session")
-    def smtp(testcontext):
+    def smtp(request):
         return smtplib.SMTP("merlinux.eu")
 
 The name of the factory is ``smtp`` (the factory function name) 
@@ -214,7 +215,7 @@
     ================================= FAILURES =================================
     ________________________________ test_ehlo _________________________________
     
-    smtp = <smtplib.SMTP instance at 0x25e7b48>
+    smtp = <smtplib.SMTP instance at 0x31bce18>
     
         def test_ehlo(smtp):
             response = smtp.ehlo()
@@ -226,7 +227,7 @@
     test_module.py:5: AssertionError
     ________________________________ test_noop _________________________________
     
-    smtp = <smtplib.SMTP instance at 0x25e7b48>
+    smtp = <smtplib.SMTP instance at 0x31bce18>
     
         def test_noop(smtp):
             response = smtp.noop()
@@ -235,7 +236,7 @@
     E       assert 0
     
     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
 the same (session-scoped) object was passed into the two test functions.
@@ -247,7 +248,7 @@
 Extending the previous example, we can flag the factory to create
 two ``smtp`` values which will cause all tests using it to
 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
     import pytest
@@ -255,11 +256,11 @@
 
     @pytest.factory(scope="session", 
                     params=["merlinux.eu", "mail.python.org"])
-    def smtp(testcontext):
-        return smtplib.SMTP(testcontext.param)
+    def smtp(request):
+        return smtplib.SMTP(request.param)
 
 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.  
 So let's just do another run::
 
@@ -269,7 +270,7 @@
     ================================= FAILURES =================================
     __________________________ test_ehlo[merlinux.eu] __________________________
     
-    smtp = <smtplib.SMTP instance at 0x2960050>
+    smtp = <smtplib.SMTP instance at 0x28dc5a8>
     
         def test_ehlo(smtp):
             response = smtp.ehlo()
@@ -281,7 +282,7 @@
     test_module.py:5: AssertionError
     __________________________ test_noop[merlinux.eu] __________________________
     
-    smtp = <smtplib.SMTP instance at 0x2960050>
+    smtp = <smtplib.SMTP instance at 0x28dc5a8>
     
         def test_noop(smtp):
             response = smtp.noop()
@@ -292,7 +293,7 @@
     test_module.py:10: AssertionError
     ________________________ test_ehlo[mail.python.org] ________________________
     
-    smtp = <smtplib.SMTP instance at 0x296a128>
+    smtp = <smtplib.SMTP instance at 0x28e3e18>
     
         def test_ehlo(smtp):
             response = smtp.ehlo()
@@ -303,7 +304,7 @@
     test_module.py:4: AssertionError
     ________________________ test_noop[mail.python.org] ________________________
     
-    smtp = <smtplib.SMTP instance at 0x296a128>
+    smtp = <smtplib.SMTP instance at 0x28e3e18>
     
         def test_noop(smtp):
             response = smtp.noop()
@@ -312,7 +313,7 @@
     E       assert 0
     
     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
 different ``smtp`` instantiations as defined on the factory.
@@ -324,7 +325,7 @@
 
 Further extending the ``smtp`` example, we now want to properly
 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::
 
     # content of conftest.py
@@ -333,12 +334,12 @@
 
     @pytest.factory(scope="session", 
                     params=["merlinux.eu", "mail.python.org"])
-    def smtp(testcontext):
-        smtp = smtplib.SMTP(testcontext.param)
+    def smtp(request):
+        smtp = smtplib.SMTP(request.param)
         def fin():
             print ("finalizing %s" % smtp)
             smtp.close()
-        testcontext.addfinalizer(fin)
+        request.addfinalizer(fin)
         return smtp
 
 We also add a print call and then run py.test without the default
@@ -347,9 +348,9 @@
     $ py.test -s -q --tb=no
     collecting ... collected 4 items
     FFFF
-    4 failed in 5.62 seconds
-    finalizing <smtplib.SMTP instance at 0x2d60368>
-    finalizing <smtplib.SMTP instance at 0x2d68e60>
+    4 failed in 6.40 seconds
+    finalizing <smtplib.SMTP instance at 0x125d3b0>
+    finalizing <smtplib.SMTP instance at 0x1265b90>
 
 We see that the two ``smtp`` instances are finalized appropriately.
 
@@ -360,8 +361,8 @@
 
     $ py.test --collectonly
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev8
-    plugins: xdist, bugzilla, cache, oejskit, cli, pep8, cov
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11
+    plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
     collecting ... collected 4 items
     <Module 'test_module.py'><Function 'test_ehlo[merlinux.eu]'>
@@ -369,7 +370,7 @@
       <Function 'test_ehlo[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
 the number of active resources at any given time.
@@ -404,15 +405,15 @@
 
     $ py.test -v test_appsetup.py
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev8 -- /home/hpk/venv/1/bin/python
-    cachedir: /home/hpk/tmp/doc-exec-414/.cache
-    plugins: xdist, bugzilla, cache, oejskit, cli, pep8, cov
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11 -- /home/hpk/venv/1/bin/python
+    cachedir: /home/hpk/tmp/doc-exec-423/.cache
+    plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
     collecting ... collected 2 items
     
     test_appsetup.py:12: test_exists[merlinux.eu] 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
 different ``App`` instances and respective smtp servers.  There is no
@@ -445,17 +446,17 @@
     import pytest
 
     @pytest.factory(scope="module", params=["mod1", "mod2"])
-    def modarg(testcontext):
-        param = testcontext.param
+    def modarg(request):
+        param = request.param
         print "create", param
         def fin():
             print "fin", param
-        testcontext.addfinalizer(fin)
+        request.addfinalizer(fin)
         return param
 
     @pytest.factory(scope="function", params=[1,2])
-    def otherarg(testcontext):
-        return testcontext.param
+    def otherarg(request):
+        return request.param
 
     def test_0(otherarg):
         print "  test0", otherarg
@@ -468,9 +469,9 @@
 
     $ py.test -v -s test_module.py
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev8 -- /home/hpk/venv/1/bin/python
-    cachedir: /home/hpk/tmp/doc-exec-414/.cache
-    plugins: xdist, bugzilla, cache, oejskit, cli, pep8, cov
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11 -- /home/hpk/venv/1/bin/python
+    cachedir: /home/hpk/tmp/doc-exec-423/.cache
+    plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
     collecting ... collected 8 items
     
     test_module.py:16: test_0[1] PASSED
@@ -501,26 +502,29 @@
 resource was executed before the ``mod2`` resource was setup.
 
 .. 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
-:ref:`@pytest.setup <setup>` marked functions.  It contains
-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).
+The ``request`` object may be received by `@pytest.factory`_ or
+:ref:`@pytest.setup <setup>` marked functions and provides methods
 
-.. 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:
 
 
-
 .. _`test generators`:
 .. _`parametrizing-tests`:
 .. _`parametrized test functions`:
@@ -566,7 +570,6 @@
 will be run three times::
 
     $ py.test -q
-
     collecting ... collected 13 items
     ....F........
     ================================= FAILURES =================================
@@ -585,7 +588,7 @@
     E        +  where 54 = eval('6*9')
     
     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 usual you can see the ``input`` and ``output`` values in the traceback.
@@ -662,11 +665,11 @@
     $ py.test -q test_compute.py
     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::
 
-    $ py.test -q --all
+    $ py.test -q --all test_compute.py
     collecting ... collected 5 items
     ....F
     ================================= FAILURES =================================
@@ -679,7 +682,7 @@
     E       assert 4 < 4
     
     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
 we'll get an error on the last one.
@@ -748,13 +751,4 @@
     use remains fully supported and existing code using it should run
     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.  
-
-


diff -r 9b4c51b4a2a02e9a3d9a33a1ec35aa3cab942e63 -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 doc/en/plugins.txt
--- a/doc/en/plugins.txt
+++ b/doc/en/plugins.txt
@@ -331,9 +331,6 @@
 Reference of objects involved in hooks
 ===========================================================
 
-.. autoclass:: _pytest.python.FuncargRequest()
-    :members:
-
 .. autoclass:: _pytest.config.Config()
     :members:
 


diff -r 9b4c51b4a2a02e9a3d9a33a1ec35aa3cab942e63 -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 doc/en/setup.txt
--- a/doc/en/setup.txt
+++ b/doc/en/setup.txt
@@ -33,7 +33,7 @@
 - fully interoperate with parametrized resources,
 - 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,
-- 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 
   teardown functions as soon as they have performed some actions 
   which need undoing, eliminating the no need for a separate 
@@ -140,8 +140,8 @@
         return GlobalResource()
 
     @pytest.setup(scope="module")
-    def setresource(testcontext, globresource):
-        testcontext.module.globresource = globresource
+    def setresource(request, globresource):
+        request.module.globresource = globresource
        
 Now any test module can access ``globresource`` as a module global::
 
@@ -178,16 +178,16 @@
             self.param = param
 
     @pytest.factory(scope="session", params=[1,2])
-    def globresource(testcontext):
-        g = GlobalResource(testcontext.param)
+    def globresource(request):
+        g = GlobalResource(request.param)
         def fin():
             print "finalizing", g
-        testcontext.addfinalizer(fin)
+        request.addfinalizer(fin)
         return g
 
     @pytest.setup(scope="module")
-    def setresource(testcontext, globresource):
-        testcontext.module.globresource = globresource
+    def setresource(request, globresource):
+        request.module.globresource = globresource
 
 And then re-run our test module::
 


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


diff -r 9b4c51b4a2a02e9a3d9a33a1ec35aa3cab942e63 -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 tox.ini
--- a/tox.ini
+++ b/tox.ini
@@ -62,6 +62,9 @@
 [testenv:py32]
 deps=py>=1.4.0
 
+[testenv:py33]
+deps=py>=1.4.0
+
 [testenv:jython]
 changedir=testing
 commands=



https://bitbucket.org/hpk42/pytest/changeset/d8c3798f196e/
changeset:   d8c3798f196e
user:        hpk42
date:        2012-09-17 17:32:23
summary:     introduce a new "markers" attribute to nodes and the request object. It is
a dynamic class making holdin
affected #:  8 files

diff -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 -r d8c3798f196e6aeabbc6c84c360483ebeb7c5499 CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,9 @@
 Changes between 2.2.4 and 2.3.0.dev
 -----------------------------------
 
+- introduce a generic "markers" object on Nodes and request objects
+  to allow for reading and manipulation of MarkInfo objects previously
+  set with calls to pytest.mark.* classes.
 - fix issue185 monkeypatching time.time does not cause pytest to fail
 - fix issue172 duplicate call of pytest.setup-decoratored setup_module
   functions


diff -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 -r d8c3798f196e6aeabbc6c84c360483ebeb7c5499 _pytest/__init__.py
--- a/_pytest/__init__.py
+++ b/_pytest/__init__.py
@@ -1,2 +1,2 @@
 #
-__version__ = '2.3.0.dev11'
+__version__ = '2.3.0.dev12'


diff -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 -r d8c3798f196e6aeabbc6c84c360483ebeb7c5499 _pytest/main.py
--- a/_pytest/main.py
+++ b/_pytest/main.py
@@ -181,14 +181,21 @@
         #: filesystem path where this node was collected from (can be None)
         self.fspath = getattr(parent, 'fspath', None)
 
-        #: 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)
 
+        bases = parent and (parent.markers,) or ()
+
+        #: marker class with markers from all scopes accessible as attributes
+        self.markers = type("dynmarker", bases, {self.name: True})
+
         #self.extrainit()
 
+    @property
+    def keywords(self):
+        """ dictionary of Keywords / markers on this node. """
+        return vars(self.markers)
+
     #def extrainit(self):
     #    """"extra initialization after Node is initialized.  Implemented
     #    by some subclasses. """


diff -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 -r d8c3798f196e6aeabbc6c84c360483ebeb7c5499 _pytest/python.py
--- a/_pytest/python.py
+++ b/_pytest/python.py
@@ -859,9 +859,11 @@
             self.obj = callobj
         startindex = int(self.cls is not None)
         self.funcargnames = getfuncargnames(self.obj, startindex=startindex)
-        self.keywords.update(py.builtin._getfuncdict(self.obj) or {})
+        for name, val in (py.builtin._getfuncdict(self.obj) or {}).items():
+            setattr(self.markers, name, val)
         if keywords:
-            self.keywords.update(keywords)
+            for name, val in keywords.items():
+                setattr(self.markers, name, val)
 
     @property
     def function(self):
@@ -959,6 +961,10 @@
         self.parentid = pyfuncitem.parent.nodeid
         self._factorystack = []
 
+    @property
+    def markers(self):
+        return self._getscopeitem(self.scope).markers
+
     def _getfaclist(self, argname):
         facdeflist = self._name2factory.get(argname, None)
         if facdeflist is None:
@@ -1011,7 +1017,7 @@
 
     @property
     def keywords(self):
-        """ keywords of the test function item.  """
+        """ dictionary of markers (readonly). """
         return self._pyfuncitem.keywords
 
     @property
@@ -1019,8 +1025,6 @@
         """ pytest session object. """
         return self._pyfuncitem.session
 
-
-
     def addfinalizer(self, finalizer):
         """add finalizer/teardown function to be called after the
         last test within the requesting test context finished
@@ -1047,7 +1051,8 @@
         """
         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
+        setattr(self.markers, marker.markname, marker)
+        #self._pyfuncitem.keywords[marker.markname] = marker
 
     def raiseerror(self, msg):
         """ raise a FuncargLookupError with the given message. """


diff -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 -r d8c3798f196e6aeabbc6c84c360483ebeb7c5499 doc/en/example/markers.txt
--- a/doc/en/example/markers.txt
+++ b/doc/en/example/markers.txt
@@ -26,29 +26,29 @@
 
     $ py.test -v -m webtest
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2 -- /home/hpk/venv/1/bin/python
-    cachedir: /home/hpk/tmp/doc-exec-305/.cache
-    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11 -- /home/hpk/venv/1/bin/python
+    cachedir: /home/hpk/tmp/doc-exec-426/.cache
+    plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
     collecting ... collected 2 items
     
     test_server.py:3: test_send_http PASSED
     
     =================== 1 tests deselected by "-m 'webtest'" ===================
-    ================== 1 passed, 1 deselected in 0.02 seconds ==================
+    ================== 1 passed, 1 deselected in 0.01 seconds ==================
 
 Or the inverse, running all tests except the webtest ones::
     
     $ py.test -v -m "not webtest"
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2 -- /home/hpk/venv/1/bin/python
-    cachedir: /home/hpk/tmp/doc-exec-305/.cache
-    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11 -- /home/hpk/venv/1/bin/python
+    cachedir: /home/hpk/tmp/doc-exec-426/.cache
+    plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
     collecting ... collected 2 items
     
     test_server.py:6: test_something_quick PASSED
     
     ================= 1 tests deselected by "-m 'not webtest'" =================
-    ================== 1 passed, 1 deselected in 0.02 seconds ==================
+    ================== 1 passed, 1 deselected in 0.01 seconds ==================
 
 Registering markers
 -------------------------------------
@@ -69,6 +69,8 @@
     $ py.test --markers
     @pytest.mark.webtest: mark a test as a webtest.
     
+    @pytest.mark.timeout(timeout, method=None): Set a timeout and timeout method on just one test item.  The first argument, *timeout*, is the timeout in seconds while the keyword, *method*, takes the same values as the --timeout_method option.
+    
     @pytest.mark.skipif(*conditions): skip the given test function if evaluation of all conditions has a True value.  Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. 
     
     @pytest.mark.xfail(*conditions, reason=None, run=True): mark the the test function as an expected failure. Optionally specify a reason and run=False if you don't even want to execute the test function. Any positional condition strings will be evaluated (like with skipif) and if one is False the marker will not be applied.
@@ -147,8 +149,8 @@
 
     $ py.test -k send_http  # running with the above defined examples
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
-    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11
+    plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
     collecting ... collected 4 items
     
     test_server.py .
@@ -160,8 +162,8 @@
 
     $ py.test -k-send_http
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
-    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11
+    plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
     collecting ... collected 4 items
     
     test_mark_classlevel.py ..
@@ -174,8 +176,8 @@
 
     $ py.test -kTestClass
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
-    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11
+    plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
     collecting ... collected 4 items
     
     test_mark_classlevel.py ..
@@ -208,10 +210,8 @@
             "env(name): mark test to run only on named environment")
 
     def pytest_runtest_setup(item):
-        if not isinstance(item, pytest.Function):
-            return
-        if hasattr(item.obj, 'env'):
-            envmarker = getattr(item.obj, 'env')
+        envmarker = getattr(item.markers, 'env', None)
+        if envmarker is not None:
             envname = envmarker.args[0]
             if envname != item.config.option.env:
                 pytest.skip("test requires env %r" % envname)
@@ -230,31 +230,33 @@
 
     $ py.test -E stage2
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
-    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11
+    plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
     collecting ... collected 1 items
     
     test_someenv.py s
     
-    ======================== 1 skipped in 0.02 seconds =========================
+    ======================== 1 skipped in 0.01 seconds =========================
   
 and here is one that specifies exactly the environment needed::
 
     $ py.test -E stage1
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
-    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11
+    plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
     collecting ... collected 1 items
     
     test_someenv.py .
     
-    ========================= 1 passed in 0.02 seconds =========================
+    ========================= 1 passed in 0.01 seconds =========================
 
 The ``--markers`` option always gives you a list of available markers::
 
     $ py.test --markers
     @pytest.mark.env(name): mark test to run only on named environment
     
+    @pytest.mark.timeout(timeout, method=None): Set a timeout and timeout method on just one test item.  The first argument, *timeout*, is the timeout in seconds while the keyword, *method*, takes the same values as the --timeout_method option.
+    
     @pytest.mark.skipif(*conditions): skip the given test function if evaluation of all conditions has a True value.  Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. 
     
     @pytest.mark.xfail(*conditions, reason=None, run=True): mark the the test function as an expected failure. Optionally specify a reason and run=False if you don't even want to execute the test function. Any positional condition strings will be evaluated (like with skipif) and if one is False the marker will not be applied.
@@ -264,7 +266,7 @@
     @pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible.
     
     @pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible.
-
+    
     
 Reading markers which were set from multiple places
 ----------------------------------------------------
@@ -293,7 +295,7 @@
     import sys
 
     def pytest_runtest_setup(item):
-        g = getattr(item.obj, 'glob', None)
+        g = getattr(item.markers, "glob", None)
         if g is not None:
             for info in g:
                 print ("glob args=%s kwargs=%s" %(info.args, info.kwargs))
@@ -307,7 +309,7 @@
     glob args=('class',) kwargs={'x': 2}
     glob args=('module',) kwargs={'x': 1}
     .
-    1 passed in 0.02 seconds
+    1 passed in 0.01 seconds
 
 marking platform specific tests with pytest
 --------------------------------------------------------------
@@ -330,7 +332,7 @@
     def pytest_runtest_setup(item):
         if isinstance(item, item.Function):
             plat = sys.platform
-            if not hasattr(item.obj, plat):
+            if not hasattr(item.markers, plat):
                 if ALL.intersection(set(item.obj.__dict__)):
                     pytest.skip("cannot run on platform %s" %(plat))
 
@@ -360,13 +362,13 @@
 
     $ py.test -rs # this option reports skip reasons
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
-    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11
+    plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
     collecting ... collected 4 items
     
     test_plat.py s.s.
     ========================= short test summary info ==========================
-    SKIP [2] /home/hpk/tmp/doc-exec-305/conftest.py:12: cannot run on platform linux2
+    SKIP [2] /home/hpk/tmp/doc-exec-426/conftest.py:12: cannot run on platform linux2
     
     =================== 2 passed, 2 skipped in 0.02 seconds ====================
 
@@ -374,13 +376,13 @@
 
     $ py.test -m linux2
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
-    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev11
+    plugins: xdist, bugzilla, cache, oejskit, cli, timeout, pep8, cov
     collecting ... collected 4 items
     
     test_plat.py .
     
     =================== 3 tests deselected by "-m 'linux2'" ====================
-    ================== 1 passed, 3 deselected in 0.02 seconds ==================
+    ================== 1 passed, 3 deselected in 0.01 seconds ==================
 
 then the unmarked-tests will not be run.  It is thus a way to restrict the run to the specific tests.   


diff -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 -r d8c3798f196e6aeabbc6c84c360483ebeb7c5499 setup.py
--- a/setup.py
+++ b/setup.py
@@ -24,7 +24,7 @@
         name='pytest',
         description='py.test: simple powerful testing with Python',
         long_description = long_description,
-        version='2.3.0.dev11',
+        version='2.3.0.dev12',
         url='http://pytest.org',
         license='MIT license',
         platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],


diff -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 -r d8c3798f196e6aeabbc6c84c360483ebeb7c5499 testing/test_mark.py
--- a/testing/test_mark.py
+++ b/testing/test_mark.py
@@ -318,7 +318,7 @@
             def pytest_pycollect_makeitem(__multicall__, name):
                 if name == "TestClass":
                     item = __multicall__.execute()
-                    item.keywords['xxx'] = True
+                    item.markers.xxx = True
                     return item
         """)
         for keyword in ('xxx', 'xxx test_2', 'TestClass', 'xxx -test_1',


diff -r 6a73adc3a5f025bac40f1eb0a2bd62a904951263 -r d8c3798f196e6aeabbc6c84c360483ebeb7c5499 testing/test_python.py
--- a/testing/test_python.py
+++ b/testing/test_python.py
@@ -609,7 +609,7 @@
         """)
         req = funcargs.FuncargRequest(item)
         assert req.function == item.obj
-        assert req.keywords is item.keywords
+        assert req.keywords == item.keywords
         assert hasattr(req.module, 'test_func')
         assert req.cls is None
         assert req.function.__name__ == "test_func"
@@ -716,24 +716,63 @@
         req = funcargs.FuncargRequest(item)
         assert req.fspath == modcol.fspath
 
-def test_applymarker(testdir):
-    item1,item2 = testdir.getitems("""
-        def pytest_funcarg__something(request):
-            pass
-        class TestClass:
-            def test_func1(self, something):
+class TestMarking:
+    def test_applymarker(self, testdir):
+        item1,item2 = testdir.getitems("""
+            def pytest_funcarg__something(request):
                 pass
-            def test_func2(self, something):
-                pass
-    """)
-    req1 = funcargs.FuncargRequest(item1)
-    assert 'xfail' not in item1.keywords
-    req1.applymarker(pytest.mark.xfail)
-    assert 'xfail' in item1.keywords
-    assert 'skipif' not in item1.keywords
-    req1.applymarker(pytest.mark.skipif)
-    assert 'skipif' in item1.keywords
-    pytest.raises(ValueError, "req1.applymarker(42)")
+            class TestClass:
+                def test_func1(self, something):
+                    pass
+                def test_func2(self, something):
+                    pass
+        """)
+        req1 = funcargs.FuncargRequest(item1)
+        assert 'xfail' not in item1.keywords
+        req1.applymarker(pytest.mark.xfail)
+        assert 'xfail' in item1.keywords
+        assert 'skipif' not in item1.keywords
+        req1.applymarker(pytest.mark.skipif)
+        assert 'skipif' in item1.keywords
+        pytest.raises(ValueError, "req1.applymarker(42)")
+
+    def test_accessmarker_function(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            @pytest.factory()
+            def markers(request):
+                return request.markers
+            @pytest.mark.XYZ
+            def test_function(markers):
+                assert markers.XYZ is not None
+                assert not hasattr(markers, "abc")
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=1)
+
+    def test_accessmarker_dynamic(self, testdir):
+        testdir.makeconftest("""
+            import pytest
+            @pytest.factory()
+            def markers(request):
+                return request.markers
+
+            @pytest.setup(scope="class")
+            def marking(request):
+                request.applymarker(pytest.mark.XYZ("hello"))
+        """)
+        testdir.makepyfile("""
+            import pytest
+            def test_fun1(markers):
+                assert markers.XYZ is not None
+                assert not hasattr(markers, "abc")
+            def test_fun2(markers):
+                assert markers.XYZ is not None
+                assert not hasattr(markers, "abc")
+        """)
+        reprec = testdir.inline_run()
+        reprec.assertoutcome(passed=2)
+
 
 class TestRequestCachedSetup:
     def test_request_cachedsetup_defaultmodule(self, testdir):
@@ -2365,13 +2404,14 @@
         reprec = testdir.inline_run("-v")
         reprec.assertoutcome(passed=6)
 
- at pytest.mark.parametrize(("scope", "ok", "error"),[
-    ["session", "", "fspath class function module"],
-    ["module", "module fspath", "cls function"],
-    ["class", "module fspath cls", "function"],
-    ["function", "module fspath cls function", ""]
-])
 class TestTestContextScopeAccess:
+    pytestmark = pytest.mark.parametrize(("scope", "ok", "error"),[
+        ["session", "", "fspath class function module"],
+        ["module", "module fspath", "cls function"],
+        ["class", "module fspath cls", "function"],
+        ["function", "module fspath cls function", ""]
+    ])
+
     def test_setup(self, testdir, scope, ok, error):
         testdir.makepyfile("""
             import pytest

Repository URL: https://bitbucket.org/hpk42/pytest/

--

This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.



More information about the pytest-commit mailing list