diff --git a/AUTHORS b/AUTHORS index ff02de8f6b3..8d700878cfe 100644 --- a/AUTHORS +++ b/AUTHORS @@ -28,6 +28,7 @@ Dave Hunt David Mohr Edison Gustavo Muenz Eduardo Schettino +Elizaveta Shashkova Eric Hunsberger Eric Siegerman Florian Bruhin diff --git a/CHANGELOG b/CHANGELOG index 9e8fd5157dc..2c60e84993c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -144,6 +144,8 @@ - add ``file`` and ``line`` attributes to JUnit-XML output. +- fix issue714: add ability to apply indirect=True parameter on particular argnames. + - fix issue890: changed extension of all documentation files from ``txt`` to ``rst``. Thanks to Abhijeet for the PR. diff --git a/_pytest/python.py b/_pytest/python.py index 88c7d1a39d8..89b726dd414 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -813,11 +813,12 @@ def getparam(self, name): def id(self): return "-".join(map(str, filter(None, self._idlist))) - def setmulti(self, valtype, argnames, valset, id, keywords, scopenum, + def setmulti(self, valtypes, argnames, valset, id, keywords, scopenum, param_index): for arg,val in zip(argnames, valset): self._checkargnotcontained(arg) - getattr(self, valtype)[arg] = val + valtype_for_arg = valtypes[arg] + getattr(self, valtype_for_arg)[arg] = val self.indices[arg] = param_index self._arg2scopenum[arg] = scopenum if val is _notexists: @@ -884,7 +885,7 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None, """ Add new invocations to the underlying test function using the list of argvalues for the given argnames. Parametrization is performed during the collection phase. If you need to setup expensive resources - see about setting indirect=True to do it rather at test setup time. + see about setting indirect to do it rather at test setup time. :arg argnames: a comma-separated string denoting one or more argument names, or a list/tuple of argument strings. @@ -896,7 +897,9 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None, where each tuple-element specifies a value for its respective argname. - :arg indirect: if True each argvalue corresponding to an argname will + :arg indirect: The list of argnames or boolean. A list of arguments' + names (subset of argnames). If True the list contains all names from + the argnames. Each argvalue corresponding to an argname in this list will be passed as request.param to its respective argname fixture function so that it can perform more expensive setups during the setup phase of a test rather than at collection time. @@ -941,13 +944,22 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None, if scope is None: scope = "function" scopenum = scopes.index(scope) - if not indirect: - #XXX should we also check for the opposite case? - for arg in argnames: - if arg not in self.fixturenames: - raise ValueError("%r uses no fixture %r" %( + valtypes = {} + for arg in argnames: + if arg not in self.fixturenames: + raise ValueError("%r uses no fixture %r" %(self.function, arg)) + + if indirect is True: + valtypes = dict.fromkeys(argnames, "params") + elif indirect is False: + valtypes = dict.fromkeys(argnames, "funcargs") + elif isinstance(indirect, (tuple, list)): + valtypes = dict.fromkeys(argnames, "funcargs") + for arg in indirect: + if arg not in argnames: + raise ValueError("indirect given to %r: fixture %r doesn't exist" %( self.function, arg)) - valtype = indirect and "params" or "funcargs" + valtypes[arg] = "params" idfn = None if callable(ids): idfn = ids @@ -962,7 +974,7 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None, for param_index, valset in enumerate(argvalues): assert len(valset) == len(argnames) newcallspec = callspec.copy(self) - newcallspec.setmulti(valtype, argnames, valset, ids[param_index], + newcallspec.setmulti(valtypes, argnames, valset, ids[param_index], newkeywords.get(param_index, {}), scopenum, param_index) newcalls.append(newcallspec) diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index f80643b5b50..3d0c778f5a2 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -280,6 +280,46 @@ The first invocation with ``db == "DB1"`` passed while the second with ``db == " .. regendoc:wipe +Apply indirect on particular arguments +--------------------------------------------------- + +Very often parametrization uses more than one argument name. There is opportunity to apply ``indirect`` +parameter on particular arguments. It can be done by passing list or tuple of +arguments' names to ``indirect``. In the example below there is a function ``test_indirect`` which uses +two fixtures: ``x`` and ``y``. Here we give to indirect the list, which contains the name of the +fixture ``x``. The indirect parameter will be applied to this argument only, and the value ``a`` +will be passed to respective fixture function. + + # content of test_indirect_list.py + + import pytest + @pytest.fixture(scope='function') + def x(request): + return request.param * 3 + + @pytest.fixture(scope='function') + def y(request): + return request.param * 2 + + @pytest.mark.parametrize('x, y', [('a', 'b')], indirect=['x']) + def test_indirect(x,y): + assert x == 'aaa' + assert y == 'b' + +The result of this test will be successful: + + $ py.test test_indirect_list.py --collect-only + ============================= test session starts ============================== + platform linux2 -- Python 2.7.3, pytest-2.8.0.dev4, py-1.4.30, pluggy-0.3.0 + rootdir: /home/elizabeth/work/pytest, inifile: tox.ini + collected 1 items + + + + =============================== in 0.02 seconds =============================== + +.. regendoc:wipe + Parametrizing test methods through per-class configuration -------------------------------------------------------------- diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 1966cd249ed..d072d2aeb46 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -220,17 +220,76 @@ def func(x, y): pass assert metafunc._calls[0].id == "0-2" assert metafunc._calls[1].id == "0-3" + @pytest.mark.issue714 def test_parametrize_indirect(self): def func(x, y): pass metafunc = self.Metafunc(func) metafunc.parametrize('x', [1], indirect=True) metafunc.parametrize('y', [2,3], indirect=True) - metafunc.parametrize('unnamed', [1], indirect=True) assert len(metafunc._calls) == 2 assert metafunc._calls[0].funcargs == {} assert metafunc._calls[1].funcargs == {} - assert metafunc._calls[0].params == dict(x=1,y=2, unnamed=1) - assert metafunc._calls[1].params == dict(x=1,y=3, unnamed=1) + assert metafunc._calls[0].params == dict(x=1,y=2) + assert metafunc._calls[1].params == dict(x=1,y=3) + + @pytest.mark.issue714 + def test_parametrize_indirect_list(self): + def func(x, y): pass + metafunc = self.Metafunc(func) + metafunc.parametrize('x, y', [('a', 'b')], indirect=['x']) + assert metafunc._calls[0].funcargs == dict(y='b') + assert metafunc._calls[0].params == dict(x='a') + + @pytest.mark.issue714 + def test_parametrize_indirect_list_all(self): + def func(x, y): pass + metafunc = self.Metafunc(func) + metafunc.parametrize('x, y', [('a', 'b')], indirect=['x', 'y']) + assert metafunc._calls[0].funcargs == {} + assert metafunc._calls[0].params == dict(x='a', y='b') + + @pytest.mark.issue714 + def test_parametrize_indirect_list_empty(self): + def func(x, y): pass + metafunc = self.Metafunc(func) + metafunc.parametrize('x, y', [('a', 'b')], indirect=[]) + assert metafunc._calls[0].funcargs == dict(x='a', y='b') + assert metafunc._calls[0].params == {} + + @pytest.mark.issue714 + def test_parametrize_indirect_list_functional(self, testdir): + """ + Test parametrization with 'indirect' parameter applied on + particular arguments. + + :param testdir: the instance of Testdir class, a temporary + test directory. + """ + testdir.makepyfile(""" + import pytest + @pytest.fixture(scope='function') + def x(request): + return request.param * 3 + @pytest.fixture(scope='function') + def y(request): + return request.param * 2 + @pytest.mark.parametrize('x, y', [('a', 'b')], indirect=['x']) + def test_simple(x,y): + assert len(x) == 3 + assert len(y) == 1 + """) + result = testdir.runpytest("-v") + result.stdout.fnmatch_lines([ + "*test_simple*a-b*", + "*1 passed*", + ]) + + @pytest.mark.issue714 + def test_parametrize_indirect_list_error(self, testdir): + def func(x, y): pass + metafunc = self.Metafunc(func) + with pytest.raises(ValueError): + metafunc.parametrize('x, y', [('a', 'b')], indirect=['x', 'z']) def test_addcalls_and_parametrize_indirect(self): def func(x, y): pass