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

Bitbucket commits-noreply at bitbucket.org
Wed Jul 18 19:48:55 CEST 2012


4 new commits in pytest:


https://bitbucket.org/hpk42/pytest/changeset/5ce84793fd92/
changeset:   5ce84793fd92
user:        hpk42
date:        2012-07-16 10:46:44
summary:     put automatic funcarg_ API to Py*objects only, refine internal subclassing and initialisation logic
affected #:  4 files

diff -r 227c24740b10cadfdb2f208cde64eb0702cdbeff -r 5ce84793fd929b65fd666c77093580424f7b1f11 _pytest/capture.py
--- a/_pytest/capture.py
+++ b/_pytest/capture.py
@@ -119,8 +119,9 @@
         return "", ""
 
     def activate_funcargs(self, pyfuncitem):
-        if pyfuncitem.funcargs:
-            for name, capfuncarg in pyfuncitem.funcargs.items():
+        funcargs = getattr(pyfuncitem, "funcargs", None)
+        if funcargs is not None:
+            for name, capfuncarg in funcargs.items():
                 if name in ('capsys', 'capfd'):
                     assert not hasattr(self, '_capturing_funcarg')
                     self._capturing_funcarg = capfuncarg


diff -r 227c24740b10cadfdb2f208cde64eb0702cdbeff -r 5ce84793fd929b65fd666c77093580424f7b1f11 _pytest/main.py
--- a/_pytest/main.py
+++ b/_pytest/main.py
@@ -3,7 +3,6 @@
 import py
 import pytest, _pytest
 import os, sys, imp
-from _pytest.monkeypatch import monkeypatch
 
 tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
 
@@ -151,130 +150,6 @@
 
     return property(fget)
 
-def pyobj_property(name):
-    def get(self):
-        node = self.getparent(getattr(pytest, name))
-        if node is not None:
-            return node.obj
-    doc = "python %s object this node was collected from (can be None)." % (
-          name.lower(),)
-    return property(get, None, None, doc)
-
-class Request(object):
-    _argprefix = "pytest_funcarg__"
-
-    class LookupError(LookupError):
-        """ error while performing funcarg factory lookup. """
-
-    def _initattr(self):
-        self._name2factory = {}
-        self._currentarg = None
-
-    @property
-    def _plugins(self):
-        extra = [obj for obj in (self.module, self.instance) if obj]
-        return self.getplugins() + extra
-
-    def _getscopeitem(self, scope):
-        if scope == "function":
-            return self
-        elif scope == "session":
-            return None
-        elif scope == "class":
-            x = self.getparent(pytest.Class)
-            if x is not None:
-                return x
-            scope = "module"
-        if scope == "module":
-            return self.getparent(pytest.Module)
-        raise ValueError("unknown scope %r" %(scope,))
-
-    def getfuncargvalue(self, argname):
-        """ Retrieve a named function argument value.
-
-        This function looks up a matching factory and invokes
-        it to obtain the return value.  The factory receives
-        the same request object and can itself perform recursive
-        calls to this method, effectively allowing to make use of
-        multiple other funcarg values or to decorate values from
-        other name-matching factories.
-        """
-        try:
-            return self.funcargs[argname]
-        except KeyError:
-            pass
-        except TypeError:
-            self.funcargs = getattr(self, "_funcargs", {})
-        if argname not in self._name2factory:
-            self._name2factory[argname] = self.config.pluginmanager.listattr(
-                    plugins=self._plugins,
-                    attrname=self._argprefix + str(argname)
-            )
-        #else: we are called recursively
-        if not self._name2factory[argname]:
-            self._raiselookupfailed(argname)
-        funcargfactory = self._name2factory[argname].pop()
-        oldarg = self._currentarg
-        mp = monkeypatch()
-        mp.setattr(self, '_currentarg', argname)
-        try:
-            param = self.callspec.getparam(argname)
-        except (AttributeError, ValueError):
-            pass
-        else:
-            mp.setattr(self, 'param', param, raising=False)
-        try:
-            self.funcargs[argname] = res = funcargfactory(self)
-        finally:
-            mp.undo()
-        return res
-
-    def addfinalizer(self, finalizer):
-        """ add a no-args finalizer function to be called when the underlying
-        node is torn down."""
-        self.session._setupstate.addfinalizer(finalizer, self)
-
-    def cached_setup(self, setup, teardown=None,
-                     scope="module", extrakey=None):
-        """ Return a cached testing resource created by ``setup`` &
-        detroyed by a respective ``teardown(resource)`` call.
-
-        :arg teardown: function receiving a previously setup resource.
-        :arg setup: a no-argument function creating a resource.
-        :arg scope: a string value out of ``function``, ``class``, ``module``
-            or ``session`` indicating the caching lifecycle of the resource.
-        :arg extrakey: added to internal caching key.
-        """
-        if not hasattr(self.config, '_setupcache'):
-            self.config._setupcache = {} # XXX weakref?
-        colitem = self._getscopeitem(scope)
-        cachekey = (self._currentarg, colitem, extrakey)
-        cache = self.config._setupcache
-        try:
-            val = cache[cachekey]
-        except KeyError:
-            val = setup()
-            cache[cachekey] = val
-            if teardown is not None:
-                def finalizer():
-                    del cache[cachekey]
-                    teardown(val)
-                self.session._setupstate.addfinalizer(finalizer, colitem)
-        return val
-
-    def _raiselookupfailed(self, argname):
-        available = []
-        for plugin in self._plugins:
-            for name in vars(plugin):
-                if name.startswith(self._argprefix):
-                    name = name[len(self._argprefix):]
-                    if name not in available:
-                        available.append(name)
-        fspath, lineno, msg = self.reportinfo()
-        msg = "LookupError: no factory found for function argument %r" % (argname,)
-        msg += "\n available funcargs: %s" %(", ".join(available),)
-        msg += "\n use 'py.test --funcargs [testpath]' for help on them."
-        raise self.LookupError(msg)
 
 class Node(object):
     """ base class for Collector and Item the test collection tree.
@@ -302,6 +177,12 @@
         #: fspath sensitive hook proxy used to call pytest hooks
         self.ihook = self.session.gethookproxy(self.fspath)
 
+        self.extrainit()
+
+    def extrainit(self):
+        """"extra initialization after Node is initialized.  Implemented
+        by some subclasses. """
+
     Module = compatproperty("Module")
     Class = compatproperty("Class")
     Instance = compatproperty("Instance")
@@ -309,11 +190,6 @@
     File = compatproperty("File")
     Item = compatproperty("Item")
 
-    module = pyobj_property("Module")
-    cls = pyobj_property("Class")
-    instance = pyobj_property("Instance")
-
-
     def _getcustomclass(self, name):
         cls = getattr(self, name)
         if cls != getattr(pytest, name):
@@ -476,20 +352,12 @@
 class File(FSCollector):
     """ base class for collecting tests from a file. """
 
-class Item(Node, Request):
+class Item(Node):
     """ a basic test invocation item. Note that for a single function
     there might be multiple test invocation items.
     """
     nextitem = None
 
-    def __init__(self, name, parent=None, config=None, session=None):
-        super(Item, self).__init__(name, parent, config, session)
-        self._initattr()
-        self.funcargs = None # later set to a dict from fillfuncargs() or
-                             # from getfuncargvalue().  Setting it to
-                             # None prevents users from performing
-                             # "name in item.funcargs" checks too early.
-
     def reportinfo(self):
         return self.fspath, None, ""
 
@@ -532,10 +400,9 @@
         __module__ = 'builtins' # for py3
 
     def __init__(self, config):
-        super(Session, self).__init__(py.path.local(), parent=None,
-            config=config, session=self)
-        assert self.config.pluginmanager.register(
-            self, name="session", prepend=True)
+        FSCollector.__init__(self, py.path.local(), parent=None,
+                             config=config, session=self)
+        self.config.pluginmanager.register(self, name="session", prepend=True)
         self._testsfailed = 0
         self.shouldstop = False
         self.trace = config.trace.root.get("collection")


diff -r 227c24740b10cadfdb2f208cde64eb0702cdbeff -r 5ce84793fd929b65fd666c77093580424f7b1f11 _pytest/python.py
--- a/_pytest/python.py
+++ b/_pytest/python.py
@@ -4,7 +4,7 @@
 import sys
 import pytest
 from py._code.code import TerminalRepr
-from _pytest.main import Request, Item
+from _pytest.monkeypatch import monkeypatch
 
 import _pytest
 cutdir = py.path.local(_pytest.__file__).dirpath()
@@ -24,6 +24,135 @@
             return x
     return property(get)
 
+def pyobj_property(name):
+    def get(self):
+        node = self.getparent(getattr(pytest, name))
+        if node is not None:
+            return node.obj
+    doc = "python %s object this node was collected from (can be None)." % (
+          name.lower(),)
+    return property(get, None, None, doc)
+
+class Request(object):
+    _argprefix = "pytest_funcarg__"
+
+    class LookupError(LookupError):
+        """ error while performing funcarg factory lookup. """
+
+    def extrainit(self):
+        self._name2factory = {}
+        self._currentarg = None
+        self.funcargs = None # later set to a dict from fillfuncargs() or
+                             # from getfuncargvalue().  Setting it to
+                             # None prevents users from performing
+                             # "name in item.funcargs" checks too early.
+
+    @property
+    def _plugins(self):
+        extra = [obj for obj in (self.module, self.instance) if obj]
+        return self.getplugins() + extra
+
+    def _getscopeitem(self, scope):
+        if scope == "function":
+            return self
+        elif scope == "session":
+            return None
+        elif scope == "class":
+            x = self.getparent(pytest.Class)
+            if x is not None:
+                return x
+            scope = "module"
+        if scope == "module":
+            return self.getparent(pytest.Module)
+        raise ValueError("unknown scope %r" %(scope,))
+
+    def getfuncargvalue(self, argname):
+        """ Retrieve a named function argument value.
+
+        This function looks up a matching factory and invokes
+        it to obtain the return value.  The factory receives
+        can itself perform recursive calls to this method,
+        either for using multiple other funcarg values under the hood
+        or to decorate values from other factories matching the same name.
+        """
+        try:
+            return self.funcargs[argname]
+        except KeyError:
+            pass
+        except TypeError:
+            self.funcargs = getattr(self, "_funcargs", {})
+        if argname not in self._name2factory:
+            self._name2factory[argname] = self.config.pluginmanager.listattr(
+                    plugins=self._plugins,
+                    attrname=self._argprefix + str(argname)
+            )
+        #else: we are called recursively
+        if not self._name2factory[argname]:
+            self._raiselookupfailed(argname)
+        funcargfactory = self._name2factory[argname].pop()
+        oldarg = self._currentarg
+        mp = monkeypatch()
+        mp.setattr(self, '_currentarg', argname)
+        try:
+            param = self.callspec.getparam(argname)
+        except (AttributeError, ValueError):
+            pass
+        else:
+            mp.setattr(self, 'param', param, raising=False)
+        try:
+            self.funcargs[argname] = res = funcargfactory(self)
+        finally:
+            mp.undo()
+        return res
+
+    def addfinalizer(self, finalizer):
+        """ add a no-args finalizer function to be called when the underlying
+        node is torn down."""
+        self.session._setupstate.addfinalizer(finalizer, self)
+
+    def cached_setup(self, setup, teardown=None,
+                     scope="module", extrakey=None):
+        """ Return a cached testing resource created by ``setup`` &
+        detroyed by a respective ``teardown(resource)`` call.
+
+        :arg teardown: function receiving a previously setup resource.
+        :arg setup: a no-argument function creating a resource.
+        :arg scope: a string value out of ``function``, ``class``, ``module``
+            or ``session`` indicating the caching lifecycle of the resource.
+        :arg extrakey: added to internal caching key.
+        """
+        if not hasattr(self.config, '_setupcache'):
+            self.config._setupcache = {} # XXX weakref?
+        colitem = self._getscopeitem(scope)
+        cachekey = (self._currentarg, colitem, extrakey)
+        cache = self.config._setupcache
+        try:
+            val = cache[cachekey]
+        except KeyError:
+            val = setup()
+            cache[cachekey] = val
+            if teardown is not None:
+                def finalizer():
+                    del cache[cachekey]
+                    teardown(val)
+                self.session._setupstate.addfinalizer(finalizer, colitem)
+        return val
+
+    def _raiselookupfailed(self, argname):
+        available = []
+        for plugin in self._plugins:
+            for name in vars(plugin):
+                if name.startswith(self._argprefix):
+                    name = name[len(self._argprefix):]
+                    if name not in available:
+                        available.append(name)
+        fspath, lineno, msg = self.reportinfo()
+        msg = "LookupError: no factory found for function argument %r" % (argname,)
+        msg += "\n available funcargs: %s" %(", ".join(available),)
+        msg += "\n use 'py.test --funcargs [testpath]' for help on them."
+        raise self.LookupError(msg)
+
+
 
 def pytest_addoption(parser):
     group = parser.getgroup("general")
@@ -133,8 +262,12 @@
         # assume them to not be generators
         return False
 
-class PyobjMixin(object):
+class PyobjContext(object):
+    module = pyobj_property("Module")
+    cls = pyobj_property("Class")
+    instance = pyobj_property("Instance")
 
+class PyobjMixin(Request, PyobjContext):
     def obj():
         def fget(self):
             try:
@@ -203,7 +336,7 @@
         assert isinstance(lineno, int)
         return fspath, lineno, modpath
 
-class PyCollectorMixin(PyobjMixin, pytest.Collector):
+class PyCollector(PyobjMixin, pytest.Collector):
 
     def funcnamefilter(self, name):
         for prefix in self.config.getini("python_functions"):
@@ -283,7 +416,7 @@
         else:
             pytestmark(funcobj)
 
-class Module(pytest.File, PyCollectorMixin):
+class Module(pytest.File, PyCollector):
     """ Collector for test classes and functions. """
     def _getobj(self):
         return self._memoizedcall('_obj', self._importtestmodule)
@@ -331,7 +464,7 @@
             else:
                 self.obj.teardown_module()
 
-class Class(PyCollectorMixin, pytest.Collector):
+class Class(PyCollector):
     """ Collector for test methods. """
     def collect(self):
         return [self._getcustomclass("Instance")(name="()", parent=self)]
@@ -350,7 +483,7 @@
             teardown_class = getattr(teardown_class, '__func__', teardown_class)
             teardown_class(self.obj)
 
-class Instance(PyCollectorMixin, pytest.Collector):
+class Instance(PyCollector):
     def _getobj(self):
         return self.parent.obj()
 
@@ -437,7 +570,7 @@
         tw.line("%s:%d" % (self.filename, self.firstlineno+1))
 
 
-class Generator(FunctionMixin, PyCollectorMixin, pytest.Collector):
+class Generator(FunctionMixin, PyCollector):
     def collect(self):
         # test generators are seen as collectors but they also
         # invoke setup/teardown on popular request
@@ -882,7 +1015,7 @@
     return property(get, set, None, doc)
 
 
-class OldFuncargRequest(Request):
+class OldFuncargRequest(Request, PyobjContext):
     """ (deprecated) helper interactions with a test function invocation.
 
     Note that there is an optional ``param`` attribute in case
@@ -892,9 +1025,11 @@
     """
     def __init__(self, pyfuncitem):
         self._pyfuncitem = pyfuncitem
-        Request._initattr(self)
+        Request.extrainit(self)
+        self.funcargs = pyfuncitem.funcargs
         self.getplugins = self._pyfuncitem.getplugins
         self.reportinfo = self._pyfuncitem.reportinfo
+        self.getparent = self._pyfuncitem.getparent
         try:
             self.param = self._pyfuncitem.param
         except AttributeError:
@@ -906,9 +1041,6 @@
     _getscopeitem = itemapi_property("_getscopeitem")
     funcargs = itemapi_property("funcargs", set=True)
     keywords = itemapi_property("keywords")
-    module   = itemapi_property("module")
-    cls      = itemapi_property("cls")
-    instance = itemapi_property("instance")
     config   = itemapi_property("config")
     session  = itemapi_property("session")
     fspath   = itemapi_property("fspath")


diff -r 227c24740b10cadfdb2f208cde64eb0702cdbeff -r 5ce84793fd929b65fd666c77093580424f7b1f11 testing/test_python.py
--- a/testing/test_python.py
+++ b/testing/test_python.py
@@ -1647,3 +1647,21 @@
         ])
 
 
+
+class TestResourceIntegrationFunctional:
+    def test_parametrize_with_ids(self, testdir):
+        testdir.makepyfile("""
+            import pytest
+            def pytest_generate_tests(metafunc):
+                metafunc.parametrize(("a", "b"), [(1,1), (1,2)],
+                                     ids=["basic", "advanced"])
+
+            def test_function(a, b):
+                assert a == b
+        """)
+        result = testdir.runpytest("-v")
+        assert result.ret == 1
+        result.stdout.fnmatch_lines([
+            "*test_function*basic*PASSED",
+            "*test_function*advanced*FAILED",
+        ])



https://bitbucket.org/hpk42/pytest/changeset/748fede1c05e/
changeset:   748fede1c05e
user:        hpk42
date:        2012-07-16 10:47:00
summary:     V1 of the resources API draft
affected #:  2 files

diff -r 5ce84793fd929b65fd666c77093580424f7b1f11 -r 748fede1c05eb3c2c5f181559b71843488cca839 _pytest/main.py
--- a/_pytest/main.py
+++ b/_pytest/main.py
@@ -609,3 +609,29 @@
                     for x in self.genitems(subnode):
                         yield x
             node.ihook.pytest_collectreport(report=rep)
+
+    def register_resource_factory(self, name, factoryfunc,
+                                  matchscope=None,
+                                  cachescope=None):
+        """ register a factory function for the given name.
+
+        :param name: the name which can be used to retrieve a value constructed
+                     by the factory function later.
+        :param factoryfunc: a function accepting (name, reqnode) parameters
+                     and returning a value.
+        :param matchscope: denotes visibility of the factory func.
+                     Pass a particular Node instance if you want to
+                     restrict factory function visilbility to its descendants.
+                     Pass None if you want the factory func to be globally
+                     availabile.
+        :param cachescope: denotes caching scope. If you pass a node instance
+                     the value returned by getresource() will be reused
+                     for all descendants of that node.  Pass None (the default)
+                     if you want no caching. Pass "session" if you want to
+                     to cache on a per-session level.
+        """
+
+
+
+
+


diff -r 5ce84793fd929b65fd666c77093580424f7b1f11 -r 748fede1c05eb3c2c5f181559b71843488cca839 doc/en/example/resources.txt
--- /dev/null
+++ b/doc/en/example/resources.txt
@@ -0,0 +1,167 @@
+
+V2: Creating and working with parametrized test resources
+===============================================================
+
+# XXX collection versus setup-time
+# XXX parametrize-relation?
+
+pytest-2.3 provides generalized resource management allowing
+to flexibly manage caching and parametrization across your test suite.
+
+This is draft documentation, pending refinements and changes according
+to feedback and to implementation or backward compatibility issues
+(the new mechanism is supposed to allow fully backward compatible
+operations for uses of the "funcarg" mechanism.
+
+the new global pytest_runtest_init hook
+------------------------------------------------------
+
+Prior to 2.3, pytest offered a pytest_configure and a pytest_sessionstart
+hook which was used often to setup global resources.  This suffers from
+several problems. First of all, in distributed testing the master would
+also setup test resources that are never needed because it only co-ordinates
+the test run activities of the slave processes.  Secondly, in large test
+suites resources are setup that might not be needed for the concrete test
+run.  The first issue is solved through the introduction of a specific
+hook::
+
+    def pytest_runtest_init(session):
+        # called ahead of pytest_runtestloop() test execution
+
+This hook will only be called in processes that actually run tests.
+
+The second issue is solved through a new register/getresource API which
+will only ever setup resources if they are needed.  See the following
+examples and sections on how this works.
+
+
+managing a global database resource
+---------------------------------------------------------------
+
+If you have one database object which you want to use in tests
+you can write the following into a conftest.py file::
+
+    class Database:
+        def __init__(self):
+            print ("database instance created")
+        def destroy(self):
+            print ("database instance destroyed")
+
+    def factory_db(name, node):
+        db = Database()
+        node.addfinalizer(db.destroy)
+        return db
+
+    def pytest_runtest_init(session):
+        session.register_resource("db", factory_db, atnode=session)
+
+You can then access the constructed resource in a test like this::
+
+    def test_something(db):
+        ...
+
+The "db" function argument will lead to a lookup of the respective
+factory value and be passed to the function body.  According to the
+registration, the db object will be instantiated on a per-session basis
+and thus reused across all test functions that require it.
+
+instantiating a database resource per-module
+---------------------------------------------------------------
+
+If you want one database instance per test module you can restrict
+caching by modifying the "atnode" parameter of the registration 
+call above::
+
+    def pytest_runtest_init(session):
+        session.register_resource("db", factory_db, atnode=pytest.Module)
+
+Neither the tests nor the factory function will need to change.
+This also means that you can decide the scoping of resources
+at runtime - e.g. based on a command line option: for developer
+settings you might want per-session and for Continous Integration
+runs you might prefer per-module or even per-function scope like this::
+
+    def pytest_runtest_init(session):
+        session.register_resource_factory("db", factory_db, 
+                                          atnode=pytest.Function)
+
+parametrized resources
+----------------------------------
+
+If you want to rerun tests with different resource values you can specify
+a list of factories instead of just one::
+
+    def pytest_runtest_init(session):
+        session.register_factory("db", [factory1, factory2], atnode=session)
+
+In this case all tests that depend on the "db" resource will be run twice
+using the respective values obtained from the two factory functions.
+
+
+Using a resource from another resource factory
+----------------------------------------------
+
+You can use the database resource from a another resource factory through
+the ``node.getresource()`` method.  Let's add a resource factory for
+a "db_users" table at module-level, extending the previous db-example::
+
+    def pytest_runtest_init(session):
+        ...
+        session.register_factory("db_users", createusers, atnode=module)
+
+    def createusers(name, node):
+        db = node.getresource("db")
+        table = db.create_table("users", ...)
+        node.addfinalizer(lambda: db.destroy_table("users")
+
+    def test_user_creation(db_users):
+        ...
+
+The create-users will be called for each module.  After the tests in
+that module finish execution, the table will be destroyed according
+to registered finalizer.  Note that calling getresource() for a resource
+which has a tighter scope will raise a LookupError because the
+is not available at a more general scope. Concretely, if you
+table is defined as a per-session resource and the database object as a
+per-module one, the table creation cannot work on a per-session basis.
+
+
+Setting resources as class attributes 
+-------------------------------------------
+
+If you want to make an attribute available on a test class, you can 
+use the resource_attr marker::
+
+    @pytest.mark.resource_attr("db")
+    class TestClass:
+        def test_something(self):
+            #use self.db 
+
+Note that this way of using resources can be used on unittest.TestCase
+instances as well (function arguments can not be added due to unittest 
+limitations).
+
+
+How the funcarg mechanism is implemented (internal notes)
+-------------------------------------------------------------
+
+Prior to pytest-2.3/4, pytest advertised the "funcarg" mechanism 
+which provided a subset functionality to the generalized resource management.
+In fact, the previous mechanism is implemented in terms of the new API
+and should continue to work unmodified.  It basically automates the
+registration of  factories through automatic discovery of 
+``pytest_funcarg_NAME`` function on plugins, Python modules and classes.
+
+As an example let's consider the Module.setup() method::
+
+    class Module(PyCollector):
+        def setup(self):
+            for name, func in self.obj.__dict__.items():
+                if name.startswith("pytest_funcarg__"):
+                    resourcename = name[len("pytest_funcarg__"):]
+                    self._register_factory(resourcename, 
+                                           RequestAdapter(self, name, func))
+
+The request adapater takes care to provide the pre-2.3 API for funcarg
+factories, providing request.cached_setup/addfinalizer/getfuncargvalue
+methods.



https://bitbucket.org/hpk42/pytest/changeset/62b2ea480504/
changeset:   62b2ea480504
user:        hpk42
date:        2012-07-16 10:47:41
summary:     v2 of resources API draft
affected #:  17 files

diff -r 748fede1c05eb3c2c5f181559b71843488cca839 -r 62b2ea480504673325a25020f6a83595e4d1390f doc/en/assert.txt
--- a/doc/en/assert.txt
+++ b/doc/en/assert.txt
@@ -24,7 +24,8 @@
 
     $ py.test test_assert1.py
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.1 -- pytest-2.2.4
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
+    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
     collecting ... collected 1 items
     
     test_assert1.py F
@@ -38,7 +39,7 @@
     E        +  where 3 = f()
     
     test_assert1.py:5: AssertionError
-    ========================= 1 failed in 0.01 seconds =========================
+    ========================= 1 failed in 0.02 seconds =========================
 
 py.test has support for showing the values of the most common subexpressions
 including calls, attributes, comparisons, and binary and unary
@@ -106,7 +107,8 @@
 
     $ py.test test_assert2.py
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.1 -- pytest-2.2.4
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
+    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
     collecting ... collected 1 items
     
     test_assert2.py F
@@ -125,7 +127,7 @@
     E         '5'
     
     test_assert2.py:5: AssertionError
-    ========================= 1 failed in 0.01 seconds =========================
+    ========================= 1 failed in 0.02 seconds =========================
 
 Special comparisons are done for a number of cases:
 
@@ -182,7 +184,7 @@
    E            vals: 1 != 2
    
    test_foocompare.py:8: AssertionError
-   1 failed in 0.01 seconds
+   1 failed in 0.02 seconds
 
 .. _assert-details:
 .. _`assert introspection`:


diff -r 748fede1c05eb3c2c5f181559b71843488cca839 -r 62b2ea480504673325a25020f6a83595e4d1390f doc/en/builtin.txt
--- a/doc/en/builtin.txt
+++ b/doc/en/builtin.txt
@@ -28,7 +28,8 @@
 
     $ py.test --funcargs
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.1 -- pytest-2.2.4
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
+    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
     collected 0 items
     pytestconfig
         the pytest config object with access to command line opts.
@@ -76,5 +77,7 @@
         See http://docs.python.org/library/warnings.html for information
         on warning categories.
         
+    cov
+        A pytest funcarg that provides access to the underlying coverage object.
     
-    =============================  in 0.00 seconds =============================
+    =============================  in 0.01 seconds =============================


diff -r 748fede1c05eb3c2c5f181559b71843488cca839 -r 62b2ea480504673325a25020f6a83595e4d1390f doc/en/capture.txt
--- a/doc/en/capture.txt
+++ b/doc/en/capture.txt
@@ -64,7 +64,8 @@
 
     $ py.test
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.1 -- pytest-2.2.4
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
+    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
     collecting ... collected 2 items
     
     test_module.py .F
@@ -78,8 +79,8 @@
     
     test_module.py:9: AssertionError
     ----------------------------- Captured stdout ------------------------------
-    setting up <function test_func2 at 0x20160c8>
-    ==================== 1 failed, 1 passed in 0.01 seconds ====================
+    setting up <function test_func2 at 0x228faa0>
+    ==================== 1 failed, 1 passed in 0.02 seconds ====================
 
 Accessing captured output from a test function
 ---------------------------------------------------


diff -r 748fede1c05eb3c2c5f181559b71843488cca839 -r 62b2ea480504673325a25020f6a83595e4d1390f doc/en/conf.py
--- a/doc/en/conf.py
+++ b/doc/en/conf.py
@@ -17,7 +17,7 @@
 #
 # The full version, including alpha/beta/rc tags.
 # The short X.Y version.
-version = release = "2.3.0.dev1"
+version = release = "2.3.0.dev5"
 
 import sys, os
 


diff -r 748fede1c05eb3c2c5f181559b71843488cca839 -r 62b2ea480504673325a25020f6a83595e4d1390f doc/en/contents.txt
--- a/doc/en/contents.txt
+++ b/doc/en/contents.txt
@@ -23,4 +23,5 @@
    :hidden:
 
    changelog.txt
+   examples/resources.txt
 


diff -r 748fede1c05eb3c2c5f181559b71843488cca839 -r 62b2ea480504673325a25020f6a83595e4d1390f doc/en/doctest.txt
--- a/doc/en/doctest.txt
+++ b/doc/en/doctest.txt
@@ -44,9 +44,10 @@
 
     $ py.test
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.1 -- pytest-2.2.4
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
+    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
     collecting ... collected 1 items
     
     mymodule.py .
     
-    ========================= 1 passed in 0.02 seconds =========================
+    ========================= 1 passed in 0.07 seconds =========================


diff -r 748fede1c05eb3c2c5f181559b71843488cca839 -r 62b2ea480504673325a25020f6a83595e4d1390f doc/en/example/markers.txt
--- a/doc/en/example/markers.txt
+++ b/doc/en/example/markers.txt
@@ -26,25 +26,29 @@
 
     $ py.test -v -m webtest
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.3 -- pytest-2.2.5.dev1 -- /home/hpk/venv/1/bin/python
+    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
     collecting ... collected 2 items
     
     test_server.py:3: test_send_http PASSED
     
     =================== 1 tests deselected by "-m 'webtest'" ===================
-    ================== 1 passed, 1 deselected in 0.00 seconds ==================
+    ================== 1 passed, 1 deselected in 0.02 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.2.5.dev1 -- /home/hpk/venv/1/bin/python
+    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
     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.01 seconds ==================
+    ================== 1 passed, 1 deselected in 0.02 seconds ==================
 
 Registering markers
 -------------------------------------
@@ -143,38 +147,41 @@
 
     $ py.test -k send_http  # running with the above defined examples
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.3 -- pytest-2.2.5.dev1
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
+    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
     collecting ... collected 4 items
     
     test_server.py .
     
     =================== 3 tests deselected by '-ksend_http' ====================
-    ================== 1 passed, 3 deselected in 0.01 seconds ==================
+    ================== 1 passed, 3 deselected in 0.02 seconds ==================
 
 And you can also run all tests except the ones that match the keyword::
 
     $ py.test -k-send_http
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.3 -- pytest-2.2.5.dev1
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
+    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
     collecting ... collected 4 items
     
     test_mark_classlevel.py ..
     test_server.py .
     
     =================== 1 tests deselected by '-k-send_http' ===================
-    ================== 3 passed, 1 deselected in 0.01 seconds ==================
+    ================== 3 passed, 1 deselected in 0.02 seconds ==================
 
 Or to only select the class::
 
     $ py.test -kTestClass
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.3 -- pytest-2.2.5.dev1
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
+    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
     collecting ... collected 4 items
     
     test_mark_classlevel.py ..
     
     =================== 2 tests deselected by '-kTestClass' ====================
-    ================== 2 passed, 2 deselected in 0.01 seconds ==================
+    ================== 2 passed, 2 deselected in 0.02 seconds ==================
 
 .. _`adding a custom marker from a plugin`:
 
@@ -223,23 +230,25 @@
 
     $ py.test -E stage2
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.3 -- pytest-2.2.5.dev1
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
+    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
     collecting ... collected 1 items
     
     test_someenv.py s
     
-    ======================== 1 skipped in 0.01 seconds =========================
+    ======================== 1 skipped in 0.02 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.2.5.dev1
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
+    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
     collecting ... collected 1 items
     
     test_someenv.py .
     
-    ========================= 1 passed in 0.01 seconds =========================
+    ========================= 1 passed in 0.02 seconds =========================
 
 The ``--markers`` option always gives you a list of available markers::
 
@@ -298,7 +307,7 @@
     glob args=('class',) kwargs={'x': 2}
     glob args=('module',) kwargs={'x': 1}
     .
-    1 passed in 0.01 seconds
+    1 passed in 0.02 seconds
 
 marking platform specific tests with pytest
 --------------------------------------------------------------
@@ -351,25 +360,27 @@
 
     $ py.test -rs # this option reports skip reasons
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.3 -- pytest-2.2.5.dev1
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
+    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
     collecting ... collected 4 items
     
     test_plat.py s.s.
     ========================= short test summary info ==========================
-    SKIP [2] /home/hpk/tmp/doc-exec-222/conftest.py:12: cannot run on platform linux2
+    SKIP [2] /home/hpk/tmp/doc-exec-305/conftest.py:12: cannot run on platform linux2
     
-    =================== 2 passed, 2 skipped in 0.01 seconds ====================
+    =================== 2 passed, 2 skipped in 0.02 seconds ====================
 
 Note that if you specify a platform via the marker-command line option like this::
 
     $ py.test -m linux2
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.3 -- pytest-2.2.5.dev1
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
+    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
     collecting ... collected 4 items
     
     test_plat.py .
     
     =================== 3 tests deselected by "-m 'linux2'" ====================
-    ================== 1 passed, 3 deselected in 0.01 seconds ==================
+    ================== 1 passed, 3 deselected in 0.02 seconds ==================
 
 then the unmarked-tests will not be run.  It is thus a way to restrict the run to the specific tests.   


diff -r 748fede1c05eb3c2c5f181559b71843488cca839 -r 62b2ea480504673325a25020f6a83595e4d1390f doc/en/example/mysetup.txt
--- a/doc/en/example/mysetup.txt
+++ b/doc/en/example/mysetup.txt
@@ -49,7 +49,8 @@
 
     $ py.test test_sample.py
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.1 -- pytest-2.2.4
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
+    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
     collecting ... collected 1 items
     
     test_sample.py F
@@ -57,7 +58,7 @@
     ================================= FAILURES =================================
     _______________________________ test_answer ________________________________
     
-    mysetup = <conftest.MySetup instance at 0x17f21b8>
+    mysetup = <conftest.MySetup instance at 0x27e5320>
     
         def test_answer(mysetup):
             app = mysetup.myapp()
@@ -66,7 +67,7 @@
     E       assert 54 == 42
     
     test_sample.py:4: AssertionError
-    ========================= 1 failed in 0.01 seconds =========================
+    ========================= 1 failed in 0.02 seconds =========================
 
 This means that our ``mysetup`` object was successfully instantiated
 and ``mysetup.app()`` returned an initialized ``MyApp`` instance.
@@ -122,14 +123,15 @@
 
     $ py.test test_ssh.py -rs
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.1 -- pytest-2.2.4
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
+    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
     collecting ... collected 1 items
     
     test_ssh.py s
     ========================= short test summary info ==========================
-    SKIP [1] /tmp/doc-exec-220/conftest.py:22: specify ssh host with --ssh
+    SKIP [1] /home/hpk/tmp/doc-exec-306/conftest.py:22: specify ssh host with --ssh
     
-    ======================== 1 skipped in 0.01 seconds =========================
+    ======================== 1 skipped in 0.02 seconds =========================
 
 If you specify a command line option like ``py.test --ssh=python.org`` the test will execute as expected.
 


diff -r 748fede1c05eb3c2c5f181559b71843488cca839 -r 62b2ea480504673325a25020f6a83595e4d1390f doc/en/example/nonpython.txt
--- a/doc/en/example/nonpython.txt
+++ b/doc/en/example/nonpython.txt
@@ -27,7 +27,8 @@
 
     nonpython $ py.test test_simple.yml
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.1 -- pytest-2.2.4
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
+    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
     collecting ... collected 2 items
     
     test_simple.yml .F
@@ -37,7 +38,7 @@
     usecase execution failed
        spec failed: 'some': 'other'
        no further details known at this point.
-    ==================== 1 failed, 1 passed in 0.06 seconds ====================
+    ==================== 1 failed, 1 passed in 0.11 seconds ====================
 
 You get one dot for the passing ``sub1: sub1`` check and one failure.
 Obviously in the above ``conftest.py`` you'll want to implement a more
@@ -56,7 +57,9 @@
 
     nonpython $ py.test -v
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.1 -- pytest-2.2.4 -- /home/hpk/venv/0/bin/python
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2 -- /home/hpk/venv/1/bin/python
+    cachedir: /home/hpk/p/pytest/doc/en/.cache
+    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
     collecting ... collected 2 items
     
     test_simple.yml:1: usecase: ok PASSED
@@ -67,17 +70,18 @@
     usecase execution failed
        spec failed: 'some': 'other'
        no further details known at this point.
-    ==================== 1 failed, 1 passed in 0.06 seconds ====================
+    ==================== 1 failed, 1 passed in 0.04 seconds ====================
 
 While developing your custom test collection and execution it's also
 interesting to just look at the collection tree::
 
     nonpython $ py.test --collectonly
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.1 -- pytest-2.2.4
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
+    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
     collecting ... collected 2 items
     <YamlFile 'test_simple.yml'><YamlItem 'ok'><YamlItem 'hello'>
     
-    =============================  in 0.07 seconds =============================
+    =============================  in 0.04 seconds =============================


diff -r 748fede1c05eb3c2c5f181559b71843488cca839 -r 62b2ea480504673325a25020f6a83595e4d1390f doc/en/example/resources.txt
--- a/doc/en/example/resources.txt
+++ b/doc/en/example/resources.txt
@@ -2,38 +2,65 @@
 V2: Creating and working with parametrized test resources
 ===============================================================
 
-# XXX collection versus setup-time
-# XXX parametrize-relation?
+pytest-2.X provides generalized resource parametrization, unifying
+and extending all existing funcarg and parametrization features of
+previous pytest versions.  Existing test suites and plugins written
+for previous pytest versions shall run unmodified.
 
-pytest-2.3 provides generalized resource management allowing
-to flexibly manage caching and parametrization across your test suite.
+This V2 draft focuses on incorporating feedback provided by Floris Bruynooghe, 
+Carl Meyer and Ronny Pfannschmidt. It remains as draft documentation, pending 
+further refinements and changes according to implementation or backward 
+compatibility issues. The main changes to V1 are:
 
-This is draft documentation, pending refinements and changes according
-to feedback and to implementation or backward compatibility issues
-(the new mechanism is supposed to allow fully backward compatible
-operations for uses of the "funcarg" mechanism.
+* changed API names (atnode -> scopenode)
+* register_factory now happens at Node.collect_init() or pytest_collection_init
+  time.  It will raise an Error if called during the runtestloop
+  (which performs setup/call/teardown for each collected test).
+* new examples and notes related to @parametrize and metafunc.parametrize()
+* use 2.X as the version for introduction - not sure if 2.3 or 2.4 will
+  actually bring it.
+* examples/uses which were previously not possible to implement easily
+  are marked with "NEW" in the title.
 
-the new global pytest_runtest_init hook
+(NEW) the init_collection and init_runtestloop hooks
 ------------------------------------------------------
 
-Prior to 2.3, pytest offered a pytest_configure and a pytest_sessionstart
-hook which was used often to setup global resources.  This suffers from
-several problems. First of all, in distributed testing the master would
-also setup test resources that are never needed because it only co-ordinates
-the test run activities of the slave processes.  Secondly, in large test
-suites resources are setup that might not be needed for the concrete test
-run.  The first issue is solved through the introduction of a specific
-hook::
+pytest for a long time offers a pytest_configure and a pytest_sessionstart
+hook which are often used to setup global resources.  This suffers from
+several problems:
 
-    def pytest_runtest_init(session):
-        # called ahead of pytest_runtestloop() test execution
+1. in distributed testing the master process would setup test resources
+   that are never needed because it only co-ordinates the test run
+   activities of the slave processes.  
 
-This hook will only be called in processes that actually run tests.
+2. In large test suites resources are created which might not be needed 
+   for the concrete test run.  
 
-The second issue is solved through a new register/getresource API which
-will only ever setup resources if they are needed.  See the following
-examples and sections on how this works.
+3. Thirdly, even if you only perform a collection (with "--collectonly") 
+   resource-setup will be executed.  
 
+4. there is no place way to allow global parametrized collection and setup 
+
+The existing hooks are not a good place regarding these issues. pytest-2.X 
+solves all these issues through the introduction of two specific hooks
+(and the new register_factory/getresource API)::
+
+    def pytest_init_collection(session):
+        # called ahead of pytest_collection, which implements the
+        # collection process
+        
+    def pytest_init_runtestloop(session):
+        # called ahead of pytest_runtestloop() which executes the
+        # setup and calling of tests
+
+The pytest_init_collection hook can be used for registering resources,
+see `global resource management`_ and `parametrizing global resources`_.  
+
+The init_runtests can be used to setup and/or interact with global 
+resources.  If you just use a global resource, you may explicitely
+use it in a function argument or through a `class resource attribute`_.
+
+.. _`global resource management`:
 
 managing a global database resource
 ---------------------------------------------------------------
@@ -41,6 +68,8 @@
 If you have one database object which you want to use in tests
 you can write the following into a conftest.py file::
 
+    # contest of conftest.py
+
     class Database:
         def __init__(self):
             print ("database instance created")
@@ -52,51 +81,71 @@
         node.addfinalizer(db.destroy)
         return db
 
-    def pytest_runtest_init(session):
-        session.register_resource("db", factory_db, atnode=session)
+    def pytest_init_collection(session):
+        session.register_factory("db", factory_db)
 
-You can then access the constructed resource in a test like this::
+You can then access the constructed resource in a test by specifying
+the pre-registered name in your function definition::
 
     def test_something(db):
         ...
 
-The "db" function argument will lead to a lookup of the respective
-factory value and be passed to the function body.  According to the
-registration, the db object will be instantiated on a per-session basis
-and thus reused across all test functions that require it.
+The "db" function argument will lead to a lookup and call of the respective
+factory function and its result will be passed to the function body.  
+As the factory is registered on the session, it will by default only
+get called once per session and its value will thus be re-used across
+the whole test session.  
 
-instantiating a database resource per-module
+Previously, factories would need to call the ``request.cached_setup()``
+method to manage caching.  Here is how we could implement the above 
+with traditional funcargs::
+
+    # content of conftest.py 
+    class DataBase: 
+        ... as above
+
+    def pytest_funcarg__db(request):
+        return request.cached_setup(setup=DataBase, 
+                                    teardown=lambda db: db.destroy,
+                                    scope="session")
+
+As the funcarg factory is automatically registered by detecting its
+name and because it is called each time "db" is requested, it needs 
+to care for caching itself, here by calling the cached_setup() method
+to manage it.  As it encodes the caching scope in the factory code body,
+py.test has no way to report this via e. g. "py.test --funcargs".
+More seriously, it's not exactly trivial to provide parametrization: 
+we would need to add a "parametrize" decorator where the resource is
+used or implement a pytest_generate_tests(metafunc) hook to
+call metafunc.parametrize() with the "db" argument, and then the 
+factory would need to care to pass the appropriate "extrakey" into 
+cached_setup().  By contrast, the new way just requires a modified
+call to register factories::
+
+    def pytest_init_collection(session):
+        session.register_factory("db", [factory_mysql, factory_pg])
+
+and no other code needs to change or get decorated.
+
+(NEW) instantiating one database for each test module
 ---------------------------------------------------------------
 
 If you want one database instance per test module you can restrict
-caching by modifying the "atnode" parameter of the registration 
-call above::
+caching by modifying the "scopenode" parameter of the registration 
+call above:
 
-    def pytest_runtest_init(session):
-        session.register_resource("db", factory_db, atnode=pytest.Module)
+    def pytest_init_collection(session):
+        session.register_factory("db", factory_db, scopenode=pytest.Module)
 
 Neither the tests nor the factory function will need to change.
-This also means that you can decide the scoping of resources
-at runtime - e.g. based on a command line option: for developer
-settings you might want per-session and for Continous Integration
-runs you might prefer per-module or even per-function scope like this::
+This means that you can decide the scoping of resources at runtime -
+e.g. based on a command line option: for developer settings you might
+want per-session and for Continous Integration runs you might prefer
+per-module or even per-function scope like this::
 
-    def pytest_runtest_init(session):
-        session.register_resource_factory("db", factory_db, 
-                                          atnode=pytest.Function)
-
-parametrized resources
-----------------------------------
-
-If you want to rerun tests with different resource values you can specify
-a list of factories instead of just one::
-
-    def pytest_runtest_init(session):
-        session.register_factory("db", [factory1, factory2], atnode=session)
-
-In this case all tests that depend on the "db" resource will be run twice
-using the respective values obtained from the two factory functions.
-
+    def pytest_init_collection(session):
+        session.register_factory("db", factory_db, 
+                                 scopenode=pytest.Function)
 
 Using a resource from another resource factory
 ----------------------------------------------
@@ -105,9 +154,11 @@
 the ``node.getresource()`` method.  Let's add a resource factory for
 a "db_users" table at module-level, extending the previous db-example::
 
-    def pytest_runtest_init(session):
+    def pytest_init_collection(session):
         ...
-        session.register_factory("db_users", createusers, atnode=module)
+        # this factory will be using a scopenode=pytest.Module because
+        # it is defined in a test module.
+        session.register_factory("db_users", createusers)
 
     def createusers(name, node):
         db = node.getresource("db")
@@ -125,43 +176,194 @@
 table is defined as a per-session resource and the database object as a
 per-module one, the table creation cannot work on a per-session basis.
 
+amending/decorating a resource / funcarg__ compatibility
+----------------------------------------------------------------------
 
-Setting resources as class attributes 
+If you want to decorate a session-registered resource with
+a test-module one, you can do the following::
+
+    # content of conftest.py
+    def pytest_init_collection(session):
+        session.register_factory("db_users", createusers)
+
+This will register a db_users method on a per-session basis.
+If you want to create a dummy user such that all test
+methods in a test module can work with it::
+
+    # content of test_user_admin.py
+    def setup_class(cls, db_users):
+
+    def pytest_init_collection(session):
+        session.register_factory("db_users", createcreate_users, 
+                                 scopenode=pytest.Module)
+
+    def create_users(name, node):
+        # get the session-managed resource
+        db_users = node.getresource(name)
+        # add a user and define a remove_user undo function
+        ...
+        node.addfinalizer(remove_user)
+        return db_users
+
+    def test_user_fields(db_users):
+        # work with db_users with a pre-created entry
+        ...
+
+Using the pytest_funcarg__ mechanism, you can do the equivalent::
+
+    # content of test_user_admin.py
+
+    def pytest_funcarg__db_users(request):
+        def create_user():
+            db_users = request.getfuncargvalue("db_users")
+            # add a user
+            return db_users
+        def remove_user(db_users):
+            ...
+        return request.cached_setup(create_user, remove_user, scope="module")
+
+As the funcarg mechanism is implemented in terms of the new API
+it's also possible to mix - use register_factory/getresource at plugin-level
+and pytest_funcarg__ factories at test module level.
+
+As discussed previously with `global resource management`_, the funcarg-factory
+does not easily extend to provide parametrization. 
+
+
+.. _`class resource attributes`:
+
+(NEW) Setting resources as class attributes 
 -------------------------------------------
 
 If you want to make an attribute available on a test class, you can 
-use the resource_attr marker::
+use a new mark::
 
-    @pytest.mark.resource_attr("db")
+    @pytest.mark.class_resource("db")
     class TestClass:
         def test_something(self):
             #use self.db 
 
-Note that this way of using resources can be used on unittest.TestCase
-instances as well (function arguments can not be added due to unittest 
-limitations).
+Note that this way of using resources work with unittest.TestCase-style
+tests as well.  If you have defined "db" as a parametrized resource,
+the functions of the Test class will be run multiple times with different
+values found in "self.db".
 
+Previously, pytest could not offer its resource management features
+since those were tied to passing function arguments ("funcargs") and
+this cannot be easily integrated with the unittest framework and its
+common per-project customizations. 
 
-How the funcarg mechanism is implemented (internal notes)
+
+.. _`parametrizing global resources`:
+
+(NEW) parametrizing global resources
+----------------------------------------------------
+
+If you want to rerun tests with different resource values you can specify
+a list of factories instead of just one::
+
+    def pytest_init_collection(session):
+        session.register_factory("db", [factory1, factory2])
+
+In this case all tests that require the "db" resource will be run twice
+using the respective values obtained from the two factory functions.
+
+For reporting purposes you might want to also define identifiers
+for the db values::
+
+    def pytest_init_collection(session):
+        session.register_factory("db", [factory1, factory2],
+                                 ids=["mysql", "pg"])
+
+This will make pytest use the respective id values when reporting
+nodeids.
+
+
+(New) Declaring resource usage / implicit parametrization
+----------------------------------------------------------
+
+Sometimes you may have a resource that can work in multiple variants,
+like using different database backends. As another use-case,
+pytest's own test suite uses a "testdir" funcarg which helps to setup
+example scenarios, perform a subprocess-pytest run and check the output.
+However, there are many features that should also work with the pytest-xdist
+mode, distributing tests to multiple CPUs or hosts.  The invocation
+variants are not visible in the function signature and cannot be easily
+addressed through a "parametrize" decorator or call.  Nevertheless we want 
+to have both invocation variants to be collected and executed. 
+
+The solution is to tell pytest that you are using a resource implicitely::
+
+    @pytest.mark.uses_resource("invocation-option")
+    class TestClass:
+        def test_method(self, testdir):
+            ...
+
+When the testdir factory gets the parametrized "invocation-option"
+resource, it will see different values, depending on what the respective
+factories provide.  To register the invocation-mode factory you would write::
+
+    # content of conftest.py
+    def pytest_init_collection(session):
+        session.register_factory("invocation-option", 
+                                 [lambda **kw: "", lambda **kw: "-n1"])
+
+The testdir factory can then access it easily::
+
+    option = node.getresource("invocation-option", "")
+    ...
+
+.. note::
+    
+   apart from the "uses_resource" decoration none of the already
+   written test functions needs to be modified for the new API.
+   
+   The implicit "testdir" parametrization only happens for the tests
+   which declare use of the invocation-option resource.  All other
+   tests will get the default value passed as the second parameter
+   to node.getresource() above.  You can thus restrict 
+   running the variants to particular tests or test sets. 
+
+To conclude, these three code fragments work together to allow efficient 
+cross-session resource parametrization.
+
+
+Implementation and compatibility notes
+============================================================
+
+The new API is designed to support all existing resource parametrization
+and funcarg usages.  This chapter discusses implementation aspects.
+Feel free to choose ignorance and only consider the above usage-level.
+
+Implementing the funcarg mechanism in terms of the new API
 -------------------------------------------------------------
 
-Prior to pytest-2.3/4, pytest advertised the "funcarg" mechanism 
-which provided a subset functionality to the generalized resource management.
-In fact, the previous mechanism is implemented in terms of the new API
-and should continue to work unmodified.  It basically automates the
-registration of  factories through automatic discovery of 
-``pytest_funcarg_NAME`` function on plugins, Python modules and classes.
+Prior to pytest-2.X, pytest mainly advertised the "funcarg" mechanism 
+for resource management.  It provides automatic registration of
+factories through discovery of ``pytest_funcarg__NAME`` factory methods
+on plugins, test modules, classes and functions.  Those factories are be 
+called *each time* a resource (funcarg) is required, hence the support
+for a ``request.cached_setup" method which helps to cache resources 
+across calls.  Request objects internally keep a (item, requested_name,
+remaining-factories) state.  The "reamaining-factories" state is
+used for implementing decorating factories; a factory for a given
+name can call ``getfuncargvalue(name)`` to invoke the next-matching
+factory factories and then amend the return value.
 
-As an example let's consider the Module.setup() method::
+In order to implement the existing funcarg mechanism through
+the new API, the new API needs to internally keep around similar
+state.  XXX
+
+As an example let's consider the Module.setup_collect() method::
 
     class Module(PyCollector):
-        def setup(self):
+        def setup_collect(self):
             for name, func in self.obj.__dict__.items():
                 if name.startswith("pytest_funcarg__"):
                     resourcename = name[len("pytest_funcarg__"):]
-                    self._register_factory(resourcename, 
-                                           RequestAdapter(self, name, func))
+                    self.register_factory(resourcename, 
+                                          RequestAdapter(self, name, func))
 
-The request adapater takes care to provide the pre-2.3 API for funcarg
-factories, providing request.cached_setup/addfinalizer/getfuncargvalue
-methods.
+The request adapater takes care to provide the pre-2.X API for funcarg
+factories, i.e. request.cached_setup/addfinalizer/getfuncargvalue
+methods and some attributes.


diff -r 748fede1c05eb3c2c5f181559b71843488cca839 -r 62b2ea480504673325a25020f6a83595e4d1390f doc/en/funcargs.txt
--- a/doc/en/funcargs.txt
+++ b/doc/en/funcargs.txt
@@ -61,7 +61,8 @@
 
     $ py.test test_simplefactory.py
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.1 -- pytest-2.2.4
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
+    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
     collecting ... collected 1 items
     
     test_simplefactory.py F
@@ -76,7 +77,7 @@
     E       assert 42 == 17
     
     test_simplefactory.py:5: AssertionError
-    ========================= 1 failed in 0.01 seconds =========================
+    ========================= 1 failed in 0.02 seconds =========================
 
 This shows that the test function was called with a ``myfuncarg``
 argument value of ``42`` and the assert fails as expected.  Here is 
@@ -154,7 +155,8 @@
 
     $ py.test test_example.py
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.1 -- pytest-2.2.4
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
+    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
     collecting ... collected 10 items
     
     test_example.py .........F
@@ -169,7 +171,7 @@
     E       assert 9 < 9
     
     test_example.py:6: AssertionError
-    ==================== 1 failed, 9 passed in 0.02 seconds ====================
+    ==================== 1 failed, 9 passed in 0.03 seconds ====================
 
 Obviously, only when ``numiter`` has the value of ``9`` does the test fail.  Note that the ``pytest_generate_tests(metafunc)`` hook is called during
 the test collection phase which is separate from the actual test running.
@@ -177,7 +179,8 @@
 
     $ py.test --collectonly test_example.py
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.1 -- pytest-2.2.4
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
+    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
     collecting ... collected 10 items
     <Module 'test_example.py'><Function 'test_func[0]'>
@@ -191,19 +194,39 @@
       <Function 'test_func[8]'><Function 'test_func[9]'>
     
-    =============================  in 0.00 seconds =============================
+    =============================  in 0.02 seconds =============================
 
 If you want to select only the run with the value ``7`` you could do::
 
     $ py.test -v -k 7 test_example.py  # or -k test_func[7]
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.1 -- pytest-2.2.4 -- /home/hpk/venv/0/bin/python
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2 -- /home/hpk/venv/1/bin/python
+    cachedir: /home/hpk/tmp/doc-exec-271/.cache
+    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
     collecting ... collected 10 items
     
+    test_example.py:5: test_func[0] PASSED
+    test_example.py:5: test_func[1] PASSED
+    test_example.py:5: test_func[2] PASSED
+    test_example.py:5: test_func[3] PASSED
+    test_example.py:5: test_func[4] PASSED
+    test_example.py:5: test_func[5] PASSED
+    test_example.py:5: test_func[6] PASSED
     test_example.py:5: test_func[7] PASSED
+    test_example.py:5: test_func[8] PASSED
+    test_example.py:5: test_func[9] FAILED
     
-    ======================= 9 tests deselected by '-k7' ========================
-    ================== 1 passed, 9 deselected in 0.01 seconds ==================
+    ================================= FAILURES =================================
+    _______________________________ test_func[9] _______________________________
+    
+    numiter = 9
+    
+        def test_func(numiter):
+    >       assert numiter < 9
+    E       assert 9 < 9
+    
+    test_example.py:6: AssertionError
+    ==================== 1 failed, 9 passed in 0.03 seconds ====================
 
 You might want to look at :ref:`more parametrization examples <paramexamples>`.
 


diff -r 748fede1c05eb3c2c5f181559b71843488cca839 -r 62b2ea480504673325a25020f6a83595e4d1390f doc/en/getting-started.txt
--- a/doc/en/getting-started.txt
+++ b/doc/en/getting-started.txt
@@ -22,9 +22,14 @@
 To check your installation has installed the correct version::
 
     $ py.test --version
-    This is py.test version 2.2.4, imported from /home/hpk/p/pytest/pytest.py
+    This is py.test version 2.3.0.dev2, imported from /home/hpk/p/pytest/pytest.pyc
     setuptools registered plugins:
       pytest-xdist-1.8 at /home/hpk/p/pytest-xdist/xdist/plugin.pyc
+      pytest-bugzilla-0.1 at /home/hpk/tmp/eanxgeek/pytest_bugzilla.pyc
+      pytest-cache-0.9 at /home/hpk/p/pytest-cache/pytest_cache.pyc
+      oejskit-0.9.0 at /home/hpk/p/js-infrastructure/oejskit/pytest_jstests.pyc
+      pytest-pep8-1.0.1 at /home/hpk/venv/1/local/lib/python2.7/site-packages/pytest_pep8.pyc
+      pytest-cov-1.6 at /home/hpk/venv/1/local/lib/python2.7/site-packages/pytest_cov.pyc
 
 If you get an error checkout :ref:`installation issues`.
 
@@ -46,7 +51,8 @@
 
     $ py.test
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.1 -- pytest-2.2.4
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
+    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
     collecting ... collected 1 items
     
     test_sample.py F
@@ -60,7 +66,7 @@
     E        +  where 4 = func(3)
     
     test_sample.py:5: AssertionError
-    ========================= 1 failed in 0.01 seconds =========================
+    ========================= 1 failed in 0.02 seconds =========================
 
 py.test found the ``test_answer`` function by following :ref:`standard test discovery rules <test discovery>`, basically detecting the ``test_`` prefixes.  We got a failure report because our little ``func(3)`` call did not return ``5``.
 
@@ -95,7 +101,7 @@
     $ py.test -q test_sysexit.py
     collecting ... collected 1 items
     .
-    1 passed in 0.00 seconds
+    1 passed in 0.02 seconds
 
 .. todo:: For further ways to assert exceptions see the `raises`
 
@@ -126,7 +132,7 @@
     ================================= FAILURES =================================
     ____________________________ TestClass.test_two ____________________________
     
-    self = <test_class.TestClass instance at 0x1a956c8>
+    self = <test_class.TestClass instance at 0x2343830>
     
         def test_two(self):
             x = "hello"
@@ -134,7 +140,7 @@
     E       assert hasattr('hello', 'check')
     
     test_class.py:8: AssertionError
-    1 failed, 1 passed in 0.01 seconds
+    1 failed, 1 passed in 0.02 seconds
 
 The first test passed, the second failed. Again we can easily see
 the intermediate values used in the assertion, helping us to
@@ -163,7 +169,7 @@
     ================================= FAILURES =================================
     _____________________________ test_needsfiles ______________________________
     
-    tmpdir = local('/tmp/pytest-22/test_needsfiles0')
+    tmpdir = local('/home/hpk/tmp/pytest-2885/test_needsfiles0')
     
         def test_needsfiles(tmpdir):
             print tmpdir
@@ -172,8 +178,8 @@
     
     test_tmpdir.py:3: AssertionError
     ----------------------------- Captured stdout ------------------------------
-    /tmp/pytest-22/test_needsfiles0
-    1 failed in 0.01 seconds
+    /home/hpk/tmp/pytest-2885/test_needsfiles0
+    1 failed in 0.22 seconds
 
 Before the test runs, a unique-per-test-invocation temporary directory
 was created.  More info at :ref:`tmpdir handling`.


diff -r 748fede1c05eb3c2c5f181559b71843488cca839 -r 62b2ea480504673325a25020f6a83595e4d1390f doc/en/index.txt
--- a/doc/en/index.txt
+++ b/doc/en/index.txt
@@ -37,7 +37,7 @@
 
 - **integrates many common testing methods**
 
- - can integrate ``nose``, ``unittest.py`` and ``doctest.py`` style
+ - can run many ``nose``, ``unittest.py`` and ``doctest.py`` style
    tests, including running testcases made for Django and trial
  - supports extended :ref:`xUnit style setup <xunitsetup>`
  - supports domain-specific :ref:`non-python tests`


diff -r 748fede1c05eb3c2c5f181559b71843488cca839 -r 62b2ea480504673325a25020f6a83595e4d1390f doc/en/skipping.txt
--- a/doc/en/skipping.txt
+++ b/doc/en/skipping.txt
@@ -130,7 +130,8 @@
 
     example $ py.test -rx xfail_demo.py
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.1 -- pytest-2.2.4
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
+    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
     collecting ... collected 6 items
     
     xfail_demo.py xxxxxx
@@ -147,7 +148,7 @@
     XFAIL xfail_demo.py::test_hello6
       reason: reason
     
-    ======================== 6 xfailed in 0.03 seconds =========================
+    ======================== 6 xfailed in 0.04 seconds =========================
 
 .. _`evaluation of skipif/xfail conditions`:
 


diff -r 748fede1c05eb3c2c5f181559b71843488cca839 -r 62b2ea480504673325a25020f6a83595e4d1390f doc/en/tmpdir.txt
--- a/doc/en/tmpdir.txt
+++ b/doc/en/tmpdir.txt
@@ -28,7 +28,8 @@
 
     $ py.test test_tmpdir.py
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.1 -- pytest-2.2.4
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
+    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
     collecting ... collected 1 items
     
     test_tmpdir.py F
@@ -36,7 +37,7 @@
     ================================= FAILURES =================================
     _____________________________ test_create_file _____________________________
     
-    tmpdir = local('/tmp/pytest-23/test_create_file0')
+    tmpdir = local('/home/hpk/tmp/pytest-2886/test_create_file0')
     
         def test_create_file(tmpdir):
             p = tmpdir.mkdir("sub").join("hello.txt")
@@ -47,7 +48,7 @@
     E       assert 0
     
     test_tmpdir.py:7: AssertionError
-    ========================= 1 failed in 0.02 seconds =========================
+    ========================= 1 failed in 0.23 seconds =========================
 
 .. _`base temporary directory`:
 


diff -r 748fede1c05eb3c2c5f181559b71843488cca839 -r 62b2ea480504673325a25020f6a83595e4d1390f doc/en/unittest.txt
--- a/doc/en/unittest.txt
+++ b/doc/en/unittest.txt
@@ -24,7 +24,8 @@
 
     $ py.test test_unittest.py
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.1 -- pytest-2.2.4
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev2
+    plugins: xdist, bugzilla, cache, oejskit, pep8, cov
     collecting ... collected 1 items
     
     test_unittest.py F
@@ -42,7 +43,7 @@
     test_unittest.py:8: AssertionError
     ----------------------------- Captured stdout ------------------------------
     hello
-    ========================= 1 failed in 0.01 seconds =========================
+    ========================= 1 failed in 0.03 seconds =========================
 
 .. _`unittest.py style`: http://docs.python.org/library/unittest.html
 


diff -r 748fede1c05eb3c2c5f181559b71843488cca839 -r 62b2ea480504673325a25020f6a83595e4d1390f doc/en/usage.txt
--- a/doc/en/usage.txt
+++ b/doc/en/usage.txt
@@ -185,7 +185,7 @@
     $ python myinvoke.py
     collecting ... collected 0 items
     
-     in 0.00 seconds
+     in 0.01 seconds
     *** test run reporting finishing
 
 .. include:: links.inc



https://bitbucket.org/hpk42/pytest/changeset/4f076fee4f6d/
changeset:   4f076fee4f6d
user:        hpk42
date:        2012-07-18 19:48:43
summary:     ci
affected #:  1 file

diff -r 62b2ea480504673325a25020f6a83595e4d1390f -r 4f076fee4f6d9f1710701507fded8fe994d9431a doc/en/goodpractises.txt
--- a/doc/en/goodpractises.txt
+++ b/doc/en/goodpractises.txt
@@ -119,7 +119,7 @@
     setup(
         #...,
         tests_require=['pytest'],
-        cmdclass = {'test': pytest},
+        cmdclass = {'test': PyTest},
         )
 
 Now if you run::

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