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 .. sourcecode:: python
def pytest_genfuncruns(runspec): def pytest_genfunc(funcspec):
if "arg1" in runspec.funcargnames: if "arg1" in funcspec.funcargnames:
runspec.addfuncarg("arg1", 10) for value in range(3):
runspec.addfuncarg("arg1", 20) funcspec.addcall(arg1=value)
runspec.addfuncarg("arg1", 30)
def test_function(arg1): def test_function(arg1):
assert myfuncarg in (10, 20, 30) assert myfuncarg in (0, 1, 2)
Here is what happens: Here is what happens:
1. The ``pytest_genfuncruns()`` hook will be called once for each test 1. The ``pytest_genfunc()`` hook will be called once for each test
function. The if-statement makes sure that we only add function function. The if-statement makes sure that we only add calls
arguments (and runs) for functions that need it. The `runspec object`_ for functions that actually need the provided value.
provides access to context information. The `funcspec object`_ provides access to context information.
2. Subsequently the ``test_function()`` will be called three times 2. Subsequently the ``test_function()`` will be called three times
with three different values for ``arg1``. 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. ``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 ``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 which can either come from a test generator
or from a provider. or from a provider.
.. _`runspec object`: .. _`funcspec object`:
runspec objects funcspec objects
------------------------ ------------------------
Runspecs help to inspect a testfunction and Runspecs help to inspect a testfunction and
to generate tests with combinations of function argument values. 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 Attributes of funcspec objects
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
++++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++++++++++++++++++
``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 Useful Funcarg Tutorial Examples
@ -386,45 +368,35 @@ methods in a convenient way.
.. _`py.path.local`: ../path.html#local .. _`py.path.local`: ../path.html#local
.. _`combine multiple funcarg values`:
parametrize test functions with multiple func args
parametrize test functions by combining generated funcargs
-------------------------------------------------------------------------- --------------------------------------------------------------------------
Adding different funcargs will generate test calls with You can trigger calling test functions which take more
all combinations of added funcargs. Consider this example: than one function argument. Consider this example:
.. sourcecode:: python .. sourcecode:: python
def makearg1(runspec): def pytest_genfunc(funcspec):
runspec.addfuncarg("arg1", 10) for arg1 in range(2):
runspec.addfuncarg("arg1", 11) for arg2 in (10, 20):
funcspec.addcall(arg1=arg1, arg2=arg2)
def makearg2(runspec):
runspec.addfuncarg("arg2", 20)
runspec.addfuncarg("arg2", 21)
def pytest_genfuncruns(runspec):
makearg1(runspec)
makearg2(runspec)
# the actual test function # the actual test function
def test_function(arg1, arg2): def test_function(arg1, arg2):
assert arg1 in (10, 20) pass
assert arg2 in (20, 30)
Running this test module will result in ``test_function`` 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(0, 10)
test_function(10, 21) test_function(0, 20)
test_function(11, 20) test_function(1, 10)
test_function(11, 21) 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 You can mix generated function arguments and normally
@ -432,10 +404,10 @@ provided ones. Consider this module:
.. sourcecode:: python .. sourcecode:: python
def pytest_genfuncruns(runspec): def pytest_genfunc(funcspec):
if "arg1" in runspec.funcargnames: # test_function2 does not have it if "arg1" in funcspec.funcargnames: # test_function2 does not have it
runspec.addfuncarg("arg1", 10) funcspec.addcall(arg1=10)
runspec.addfuncarg("arg1", 20) funcspec.addcall(arg1=20)
def pytest_funcarg__arg2(request): def pytest_funcarg__arg2(request):
return [10, 20] return [10, 20]

View File

@ -25,7 +25,7 @@ def fillfuncargs(function):
except request.Error: except request.Error:
request._raiselookupfailed() request._raiselookupfailed()
class RunSpecs: class FuncSpecs:
def __init__(self, function, config=None, cls=None, module=None): def __init__(self, function, config=None, cls=None, module=None):
self.config = config self.config = config
self.module = module self.module = module
@ -33,22 +33,14 @@ class RunSpecs:
self.funcargnames = getfuncargnames(function) self.funcargnames = getfuncargnames(function)
self.cls = cls self.cls = cls
self.module = module self.module = module
self._combinations = [] self._calls = []
def addfuncarg(self, argname, value): def addcall(self, **kwargs):
if argname not in self.funcargnames: for argname in kwargs:
raise ValueError("function %r has no funcarg %r" %( if argname not in self.funcargnames:
self.function, argname)) raise ValueError("function %r has no funcarg %r" %(
newcombi = [] self.function, argname))
if not self._combinations: self._calls.append(kwargs)
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)
class FunctionCollector(py.test.collect.Collector): class FunctionCollector(py.test.collect.Collector):
def __init__(self, name, parent, combinations): 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. """ """ return custom item/collector for a python object in a module, or None. """
pytest_pycollect_obj.firstresult = True pytest_pycollect_obj.firstresult = True
def pytest_genfuncruns(self, runspec): def pytest_genfunc(self, funcspec):
""" generate (multiple) parametrized calls to a test function.""" """ generate (multiple) parametrized calls to a test function."""
def pytest_collectstart(self, collector): 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 # to work to get at the class
clscol = self.getclasscollector() clscol = self.getclasscollector()
cls = clscol and clscol.obj or None cls = clscol and clscol.obj or None
runspec = funcargs.RunSpecs(funcobj, config=self.config, cls=cls, module=module) funcspec = funcargs.FuncSpecs(funcobj, config=self.config, cls=cls, module=module)
gentesthook = self.config.hook.pytest_genfuncruns.clone(extralookup=module) gentesthook = self.config.hook.pytest_genfunc.clone(extralookup=module)
gentesthook(runspec=runspec) gentesthook(funcspec=funcspec)
if not runspec._combinations: if not funcspec._calls:
return self.Function(name, parent=self) return self.Function(name, parent=self)
return funcargs.FunctionCollector(name=name, return funcargs.FunctionCollector(name=name,
parent=self, combinations=runspec._combinations) parent=self, combinations=funcspec._calls)
class Module(py.test.collect.File, PyCollectorMixin): class Module(py.test.collect.File, PyCollectorMixin):
def _getobj(self): def _getobj(self):

View File

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