[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