rename hook, rename addfuncarg to addcall interface,

forget about combinations of funcargs.

--HG--
branch : trunk
This commit is contained in:
holger krekel 2009-05-12 01:38:09 +02:00
parent d9ad2cf761
commit c3f3dc653e
5 changed files with 101 additions and 153 deletions

View File

@ -50,21 +50,20 @@ is an example for running the same test function three times.
.. sourcecode:: python
def pytest_genfuncruns(runspec):
if "arg1" in runspec.funcargnames:
runspec.addfuncarg("arg1", 10)
runspec.addfuncarg("arg1", 20)
runspec.addfuncarg("arg1", 30)
def pytest_genfunc(funcspec):
if "arg1" in funcspec.funcargnames:
for value in range(3):
funcspec.addcall(arg1=value)
def test_function(arg1):
assert myfuncarg in (10, 20, 30)
assert myfuncarg in (0, 1, 2)
Here is what happens:
1. The ``pytest_genfuncruns()`` hook will be called once for each test
function. The if-statement makes sure that we only add function
arguments (and runs) for functions that need it. The `runspec object`_
provides access to context information.
1. The ``pytest_genfunc()`` hook will be called once for each test
function. The if-statement makes sure that we only add calls
for functions that actually need the provided value.
The `funcspec object`_ provides access to context information.
2. Subsequently the ``test_function()`` will be called three times
with three different values for ``arg1``.
@ -91,7 +90,7 @@ Attributes of request objects
``request.cls``: class object where the test function is defined in or None.
``runspec.module``: module object where the test function is defined in.
``funcspec.module``: module object where the test function is defined in.
``request.config``: access to command line opts and general config
@ -151,48 +150,31 @@ Test functions can have multiple arguments
which can either come from a test generator
or from a provider.
.. _`runspec object`:
.. _`funcspec object`:
runspec objects
funcspec objects
------------------------
Runspecs help to inspect a testfunction and
to generate tests with combinations of function argument values.
generating and combining funcargs
+++++++++++++++++++++++++++++++++++++++++++++++++++
Calling ``funcspec.addcall(**funcargs)`` will add
a tests function call using the given dictionary
of function arguments. This addition of a call
happens during test collection.
Calling ``runspec.addfuncarg(argname, value)`` will trigger
tests function calls with the given function
argument value. For each already existing
funcarg combination, the added funcarg value will
* be merged to the existing funcarg combination if the
new argument name isn't part of the funcarg combination yet.
* otherwise generate a new test call where the existing
funcarg combination is copied and updated
with the newly added funcarg value.
For simple usage, e.g. test functions with a single
generated function argument, each call to ``addfuncarg``
will just trigger a new call.
This scheme allows two sources to generate
function arguments independently from each other.
Attributes of runspec objects
Attributes of funcspec objects
++++++++++++++++++++++++++++++++++++++++
``runspec.funcargnames``: set of required function arguments for given function
``funcspec.funcargnames``: set of required function arguments for given function
``runspec.function``: underlying python test function
``funcspec.function``: underlying python test function
``runspec.cls``: class object where the test function is defined in or None.
``funcspec.cls``: class object where the test function is defined in or None.
``runspec.module``: the module object where the test function is defined in.
``funcspec.module``: the module object where the test function is defined in.
``runspec.config``: access to command line opts and general config
``funcspec.config``: access to command line opts and general config
Useful Funcarg Tutorial Examples
@ -386,45 +368,35 @@ methods in a convenient way.
.. _`py.path.local`: ../path.html#local
.. _`combine multiple funcarg values`:
parametrize test functions by combining generated funcargs
parametrize test functions with multiple func args
--------------------------------------------------------------------------
Adding different funcargs will generate test calls with
all combinations of added funcargs. Consider this example:
You can trigger calling test functions which take more
than one function argument. Consider this example:
.. sourcecode:: python
def makearg1(runspec):
runspec.addfuncarg("arg1", 10)
runspec.addfuncarg("arg1", 11)
def makearg2(runspec):
runspec.addfuncarg("arg2", 20)
runspec.addfuncarg("arg2", 21)
def pytest_genfuncruns(runspec):
makearg1(runspec)
makearg2(runspec)
def pytest_genfunc(funcspec):
for arg1 in range(2):
for arg2 in (10, 20):
funcspec.addcall(arg1=arg1, arg2=arg2)
# the actual test function
def test_function(arg1, arg2):
assert arg1 in (10, 20)
assert arg2 in (20, 30)
pass
Running this test module will result in ``test_function``
being called four times, in the following order::
being called four times, with the following arguments::
test_function(10, 20)
test_function(10, 21)
test_function(11, 20)
test_function(11, 21)
test_function(0, 10)
test_function(0, 20)
test_function(1, 10)
test_function(2, 20)
example: test functions with generated and provided funcargs
example: test functions with pre-generated and provided funcargs
-------------------------------------------------------------------
You can mix generated function arguments and normally
@ -432,10 +404,10 @@ provided ones. Consider this module:
.. sourcecode:: python
def pytest_genfuncruns(runspec):
if "arg1" in runspec.funcargnames: # test_function2 does not have it
runspec.addfuncarg("arg1", 10)
runspec.addfuncarg("arg1", 20)
def pytest_genfunc(funcspec):
if "arg1" in funcspec.funcargnames: # test_function2 does not have it
funcspec.addcall(arg1=10)
funcspec.addcall(arg1=20)
def pytest_funcarg__arg2(request):
return [10, 20]

View File

@ -25,7 +25,7 @@ def fillfuncargs(function):
except request.Error:
request._raiselookupfailed()
class RunSpecs:
class FuncSpecs:
def __init__(self, function, config=None, cls=None, module=None):
self.config = config
self.module = module
@ -33,22 +33,14 @@ class RunSpecs:
self.funcargnames = getfuncargnames(function)
self.cls = cls
self.module = module
self._combinations = []
self._calls = []
def addfuncarg(self, argname, value):
if argname not in self.funcargnames:
raise ValueError("function %r has no funcarg %r" %(
self.function, argname))
newcombi = []
if not self._combinations:
newcombi.append({argname:value})
else:
for combi in self._combinations:
if argname in combi:
combi = combi.copy()
newcombi.append(combi)
combi[argname] = value
self._combinations.extend(newcombi)
def addcall(self, **kwargs):
for argname in kwargs:
if argname not in self.funcargnames:
raise ValueError("function %r has no funcarg %r" %(
self.function, argname))
self._calls.append(kwargs)
class FunctionCollector(py.test.collect.Collector):
def __init__(self, name, parent, combinations):

View File

@ -53,7 +53,7 @@ class PluginHooks:
""" return custom item/collector for a python object in a module, or None. """
pytest_pycollect_obj.firstresult = True
def pytest_genfuncruns(self, runspec):
def pytest_genfunc(self, funcspec):
""" generate (multiple) parametrized calls to a test function."""
def pytest_collectstart(self, collector):

View File

@ -172,13 +172,13 @@ class PyCollectorMixin(PyobjMixin, py.test.collect.Collector):
# to work to get at the class
clscol = self.getclasscollector()
cls = clscol and clscol.obj or None
runspec = funcargs.RunSpecs(funcobj, config=self.config, cls=cls, module=module)
gentesthook = self.config.hook.pytest_genfuncruns.clone(extralookup=module)
gentesthook(runspec=runspec)
if not runspec._combinations:
funcspec = funcargs.FuncSpecs(funcobj, config=self.config, cls=cls, module=module)
gentesthook = self.config.hook.pytest_genfunc.clone(extralookup=module)
gentesthook(funcspec=funcspec)
if not funcspec._calls:
return self.Function(name, parent=self)
return funcargs.FunctionCollector(name=name,
parent=self, combinations=runspec._combinations)
parent=self, combinations=funcspec._calls)
class Module(py.test.collect.File, PyCollectorMixin):
def _getobj(self):

View File

@ -131,71 +131,58 @@ class TestRequest:
req = funcargs.FuncargRequest(item, "xxx")
assert req.fspath == modcol.fspath
class TestRunSpecs:
class TestFuncSpecs:
def test_no_funcargs(self, testdir):
def function(): pass
runspec = funcargs.RunSpecs(function)
assert not runspec.funcargnames
funcspec = funcargs.FuncSpecs(function)
assert not funcspec.funcargnames
def test_function_basic(self):
def func(arg1, arg2="qwe"): pass
runspec = funcargs.RunSpecs(func)
assert len(runspec.funcargnames) == 1
assert 'arg1' in runspec.funcargnames
assert runspec.function is func
assert runspec.cls is None
funcspec = funcargs.FuncSpecs(func)
assert len(funcspec.funcargnames) == 1
assert 'arg1' in funcspec.funcargnames
assert funcspec.function is func
assert funcspec.cls is None
def test_addfuncarg_basic(self):
def test_addcall_basic(self):
def func(arg1): pass
runspec = funcargs.RunSpecs(func)
funcspec = funcargs.FuncSpecs(func)
py.test.raises(ValueError, """
runspec.addfuncarg("notexists", 100)
funcspec.addcall(notexists=100)
""")
runspec.addfuncarg("arg1", 100)
assert len(runspec._combinations) == 1
assert runspec._combinations[0] == {'arg1': 100}
funcspec.addcall(arg1=100)
assert len(funcspec._calls) == 1
assert funcspec._calls[0] == {'arg1': 100}
def test_addfuncarg_two(self):
def test_addcall_two(self):
def func(arg1): pass
runspec = funcargs.RunSpecs(func)
runspec.addfuncarg("arg1", 100)
runspec.addfuncarg("arg1", 101)
assert len(runspec._combinations) == 2
assert runspec._combinations[0] == {'arg1': 100}
assert runspec._combinations[1] == {'arg1': 101}
def test_addfuncarg_combined(self):
runspec = funcargs.RunSpecs(lambda arg1, arg2: 0)
runspec.addfuncarg('arg1', 1)
runspec.addfuncarg('arg1', 2)
runspec.addfuncarg('arg2', 100)
combinations = runspec._combinations
assert len(combinations) == 2
assert combinations[0] == {'arg1': 1, 'arg2': 100}
assert combinations[1] == {'arg1': 2, 'arg2': 100}
runspec.addfuncarg('arg2', 101)
assert len(combinations) == 4
assert combinations[-1] == {'arg1': 2, 'arg2': 101}
funcspec = funcargs.FuncSpecs(func)
funcspec.addcall(arg1=100)
funcspec.addcall(arg1=101)
assert len(funcspec._calls) == 2
assert funcspec._calls[0] == {'arg1': 100}
assert funcspec._calls[1] == {'arg1': 101}
class TestGenfuncFunctional:
def test_attributes(self, testdir):
p = testdir.makepyfile("""
import py
def pytest_genfuncruns(runspec):
runspec.addfuncarg("runspec", runspec)
def pytest_genfunc(funcspec):
funcspec.addcall(funcspec=funcspec)
def test_function(runspec):
assert runspec.config == py.test.config
assert runspec.module.__name__ == __name__
assert runspec.function == test_function
assert runspec.cls is None
def test_function(funcspec):
assert funcspec.config == py.test.config
assert funcspec.module.__name__ == __name__
assert funcspec.function == test_function
assert funcspec.cls is None
class TestClass:
def test_method(self, runspec):
assert runspec.config == py.test.config
assert runspec.module.__name__ == __name__
def test_method(self, funcspec):
assert funcspec.config == py.test.config
assert funcspec.module.__name__ == __name__
# XXX actually have the unbound test function here?
assert runspec.function == TestClass.test_method.im_func
assert runspec.cls == TestClass
assert funcspec.function == TestClass.test_method.im_func
assert funcspec.cls == TestClass
""")
result = testdir.runpytest(p, "-v")
result.stdout.fnmatch_lines([
@ -205,10 +192,10 @@ class TestGenfuncFunctional:
def test_arg_twice(self, testdir):
testdir.makeconftest("""
class ConftestPlugin:
def pytest_genfuncruns(self, runspec):
assert "arg" in runspec.funcargnames
runspec.addfuncarg("arg", 10)
runspec.addfuncarg("arg", 20)
def pytest_genfunc(self, funcspec):
assert "arg" in funcspec.funcargnames
funcspec.addcall(arg=10)
funcspec.addcall(arg=20)
""")
p = testdir.makepyfile("""
def test_myfunc(arg):
@ -223,9 +210,9 @@ class TestGenfuncFunctional:
def test_two_functions(self, testdir):
p = testdir.makepyfile("""
def pytest_genfuncruns(runspec):
runspec.addfuncarg("arg1", 10)
runspec.addfuncarg("arg1", 20)
def pytest_genfunc(funcspec):
funcspec.addcall(arg1=10)
funcspec.addcall(arg1=20)
def test_func1(arg1):
assert arg1 == 10
@ -243,20 +230,17 @@ class TestGenfuncFunctional:
def test_genfuncarg_inmodule(self, testdir):
testdir.makeconftest("""
class ConftestPlugin:
def pytest_genfuncruns(self, runspec):
assert "arg" in runspec.funcargnames
runspec.addfuncarg("arg", 10)
def pytest_genfunc(self, funcspec):
assert "arg1" in funcspec.funcargnames
funcspec.addcall(arg1=1, arg2=2)
""")
p = testdir.makepyfile("""
def pytest_genfuncruns(runspec):
runspec.addfuncarg("arg2", 10)
runspec.addfuncarg("arg2", 20)
runspec.addfuncarg("classarg", 17)
def pytest_genfunc(funcspec):
funcspec.addcall(arg1=10, arg2=10)
class TestClass:
def test_myfunc(self, arg, arg2, classarg):
assert classarg == 17
assert arg == arg2
def test_myfunc(self, arg1, arg2):
assert arg1 == arg2
""")
result = testdir.runpytest("-v", p)
assert result.stdout.fnmatch_lines([