Skip to content

Commit c493f26

Browse files
committed
Merge branch 'Elizaveta239-master' closes #908
2 parents 553aef5 + b49bedc commit c493f26

File tree

5 files changed

+194
-14
lines changed

5 files changed

+194
-14
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Dave Hunt
2828
David Mohr
2929
Edison Gustavo Muenz
3030
Eduardo Schettino
31+
Elizaveta Shashkova
3132
Eric Hunsberger
3233
Eric Siegerman
3334
Florian Bruhin

CHANGELOG

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,14 @@
148148

149149
- add ``file`` and ``line`` attributes to JUnit-XML output.
150150

151+
- fix issue890: changed extension of all documentation files from ``txt`` to
152+
``rst``. Thanks to Abhijeet for the PR.
153+
154+
- fix issue714: add ability to apply indirect=True parameter on particular argnames.
155+
Thanks Elizaveta239.
156+
157+
- fix issue714: add ability to apply indirect=True parameter on particular argnames.
158+
151159
- fix issue890: changed extension of all documentation files from ``txt`` to
152160
``rst``. Thanks to Abhijeet for the PR.
153161

_pytest/python.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -813,11 +813,12 @@ def getparam(self, name):
813813
def id(self):
814814
return "-".join(map(str, filter(None, self._idlist)))
815815

816-
def setmulti(self, valtype, argnames, valset, id, keywords, scopenum,
816+
def setmulti(self, valtypes, argnames, valset, id, keywords, scopenum,
817817
param_index):
818818
for arg,val in zip(argnames, valset):
819819
self._checkargnotcontained(arg)
820-
getattr(self, valtype)[arg] = val
820+
valtype_for_arg = valtypes[arg]
821+
getattr(self, valtype_for_arg)[arg] = val
821822
self.indices[arg] = param_index
822823
self._arg2scopenum[arg] = scopenum
823824
if val is _notexists:
@@ -884,7 +885,7 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None,
884885
""" Add new invocations to the underlying test function using the list
885886
of argvalues for the given argnames. Parametrization is performed
886887
during the collection phase. If you need to setup expensive resources
887-
see about setting indirect=True to do it rather at test setup time.
888+
see about setting indirect to do it rather at test setup time.
888889
889890
:arg argnames: a comma-separated string denoting one or more argument
890891
names, or a list/tuple of argument strings.
@@ -896,7 +897,9 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None,
896897
where each tuple-element specifies a value for its respective
897898
argname.
898899
899-
:arg indirect: if True each argvalue corresponding to an argname will
900+
:arg indirect: The list of argnames or boolean. A list of arguments'
901+
names (subset of argnames). If True the list contains all names from
902+
the argnames. Each argvalue corresponding to an argname in this list will
900903
be passed as request.param to its respective argname fixture
901904
function so that it can perform more expensive setups during the
902905
setup phase of a test rather than at collection time.
@@ -941,13 +944,22 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None,
941944
if scope is None:
942945
scope = "function"
943946
scopenum = scopes.index(scope)
944-
if not indirect:
945-
#XXX should we also check for the opposite case?
946-
for arg in argnames:
947-
if arg not in self.fixturenames:
948-
raise ValueError("%r uses no fixture %r" %(
947+
valtypes = {}
948+
for arg in argnames:
949+
if arg not in self.fixturenames:
950+
raise ValueError("%r uses no fixture %r" %(self.function, arg))
951+
952+
if indirect is True:
953+
valtypes = dict.fromkeys(argnames, "params")
954+
elif indirect is False:
955+
valtypes = dict.fromkeys(argnames, "funcargs")
956+
elif isinstance(indirect, (tuple, list)):
957+
valtypes = dict.fromkeys(argnames, "funcargs")
958+
for arg in indirect:
959+
if arg not in argnames:
960+
raise ValueError("indirect given to %r: fixture %r doesn't exist" %(
949961
self.function, arg))
950-
valtype = indirect and "params" or "funcargs"
962+
valtypes[arg] = "params"
951963
idfn = None
952964
if callable(ids):
953965
idfn = ids
@@ -962,7 +974,7 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None,
962974
for param_index, valset in enumerate(argvalues):
963975
assert len(valset) == len(argnames)
964976
newcallspec = callspec.copy(self)
965-
newcallspec.setmulti(valtype, argnames, valset, ids[param_index],
977+
newcallspec.setmulti(valtypes, argnames, valset, ids[param_index],
966978
newkeywords.get(param_index, {}), scopenum,
967979
param_index)
968980
newcalls.append(newcallspec)

doc/en/example/parametrize.rst

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,46 @@ The first invocation with ``db == "DB1"`` passed while the second with ``db == "
280280

281281
.. regendoc:wipe
282282
283+
Apply indirect on particular arguments
284+
---------------------------------------------------
285+
286+
Very often parametrization uses more than one argument name. There is opportunity to apply ``indirect``
287+
parameter on particular arguments. It can be done by passing list or tuple of
288+
arguments' names to ``indirect``. In the example below there is a function ``test_indirect`` which uses
289+
two fixtures: ``x`` and ``y``. Here we give to indirect the list, which contains the name of the
290+
fixture ``x``. The indirect parameter will be applied to this argument only, and the value ``a``
291+
will be passed to respective fixture function.
292+
293+
# content of test_indirect_list.py
294+
295+
import pytest
296+
@pytest.fixture(scope='function')
297+
def x(request):
298+
return request.param * 3
299+
300+
@pytest.fixture(scope='function')
301+
def y(request):
302+
return request.param * 2
303+
304+
@pytest.mark.parametrize('x, y', [('a', 'b')], indirect=['x'])
305+
def test_indirect(x,y):
306+
assert x == 'aaa'
307+
assert y == 'b'
308+
309+
The result of this test will be successful:
310+
311+
$ py.test test_indirect_list.py --collect-only
312+
============================= test session starts ==============================
313+
platform linux2 -- Python 2.7.3, pytest-2.8.0.dev4, py-1.4.30, pluggy-0.3.0
314+
rootdir: /home/elizabeth/work/pytest, inifile: tox.ini
315+
collected 1 items
316+
<Module 'testing/test_argnames.py'>
317+
<Function 'test_simple[a-b]'>
318+
319+
=============================== in 0.02 seconds ===============================
320+
321+
.. regendoc:wipe
322+
283323
Parametrizing test methods through per-class configuration
284324
--------------------------------------------------------------
285325

testing/python/metafunc.py

Lines changed: 122 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -220,17 +220,136 @@ def func(x, y): pass
220220
assert metafunc._calls[0].id == "0-2"
221221
assert metafunc._calls[1].id == "0-3"
222222

223+
@pytest.mark.issue714
223224
def test_parametrize_indirect(self):
224225
def func(x, y): pass
225226
metafunc = self.Metafunc(func)
226227
metafunc.parametrize('x', [1], indirect=True)
227228
metafunc.parametrize('y', [2,3], indirect=True)
228-
metafunc.parametrize('unnamed', [1], indirect=True)
229229
assert len(metafunc._calls) == 2
230230
assert metafunc._calls[0].funcargs == {}
231231
assert metafunc._calls[1].funcargs == {}
232-
assert metafunc._calls[0].params == dict(x=1,y=2, unnamed=1)
233-
assert metafunc._calls[1].params == dict(x=1,y=3, unnamed=1)
232+
assert metafunc._calls[0].params == dict(x=1,y=2)
233+
assert metafunc._calls[1].params == dict(x=1,y=3)
234+
235+
@pytest.mark.issue714
236+
def test_parametrize_indirect_list(self):
237+
def func(x, y): pass
238+
metafunc = self.Metafunc(func)
239+
metafunc.parametrize('x, y', [('a', 'b')], indirect=['x'])
240+
assert metafunc._calls[0].funcargs == dict(y='b')
241+
assert metafunc._calls[0].params == dict(x='a')
242+
243+
@pytest.mark.issue714
244+
def test_parametrize_indirect_list_all(self):
245+
def func(x, y): pass
246+
metafunc = self.Metafunc(func)
247+
metafunc.parametrize('x, y', [('a', 'b')], indirect=['x', 'y'])
248+
assert metafunc._calls[0].funcargs == {}
249+
assert metafunc._calls[0].params == dict(x='a', y='b')
250+
251+
@pytest.mark.issue714
252+
def test_parametrize_indirect_list_empty(self):
253+
def func(x, y): pass
254+
metafunc = self.Metafunc(func)
255+
metafunc.parametrize('x, y', [('a', 'b')], indirect=[])
256+
assert metafunc._calls[0].funcargs == dict(x='a', y='b')
257+
assert metafunc._calls[0].params == {}
258+
259+
@pytest.mark.issue714
260+
def test_parametrize_indirect_list_functional(self, testdir):
261+
"""
262+
Test parametrization with 'indirect' parameter applied on
263+
particular arguments. As y is is direct, its value should
264+
be used directly rather than being passed to the fixture
265+
y.
266+
267+
:param testdir: the instance of Testdir class, a temporary
268+
test directory.
269+
"""
270+
testdir.makepyfile("""
271+
import pytest
272+
@pytest.fixture(scope='function')
273+
def x(request):
274+
return request.param * 3
275+
@pytest.fixture(scope='function')
276+
def y(request):
277+
return request.param * 2
278+
@pytest.mark.parametrize('x, y', [('a', 'b')], indirect=['x'])
279+
def test_simple(x,y):
280+
assert len(x) == 3
281+
assert len(y) == 1
282+
""")
283+
result = testdir.runpytest("-v")
284+
result.stdout.fnmatch_lines([
285+
"*test_simple*a-b*",
286+
"*1 passed*",
287+
])
288+
289+
@pytest.mark.issue714
290+
def test_parametrize_indirect_list_error(self, testdir):
291+
def func(x, y): pass
292+
metafunc = self.Metafunc(func)
293+
with pytest.raises(ValueError):
294+
metafunc.parametrize('x, y', [('a', 'b')], indirect=['x', 'z'])
295+
296+
@pytest.mark.issue714
297+
def test_parametrize_uses_no_fixture_error_indirect_false(self, testdir):
298+
"""The 'uses no fixture' error tells the user at collection time
299+
that the parametrize data they've set up doesn't correspond to the
300+
fixtures in their test function, rather than silently ignoring this
301+
and letting the test potentially pass.
302+
"""
303+
testdir.makepyfile("""
304+
import pytest
305+
306+
@pytest.mark.parametrize('x, y', [('a', 'b')], indirect=False)
307+
def test_simple(x):
308+
assert len(x) == 3
309+
""")
310+
result = testdir.runpytest("--collect-only")
311+
result.stdout.fnmatch_lines([
312+
"*uses no fixture 'y'*",
313+
])
314+
315+
@pytest.mark.xfail
316+
@pytest.mark.issue714
317+
def test_parametrize_uses_no_fixture_error_indirect_true(self, testdir):
318+
testdir.makepyfile("""
319+
import pytest
320+
@pytest.fixture(scope='function')
321+
def x(request):
322+
return request.param * 3
323+
@pytest.fixture(scope='function')
324+
def y(request):
325+
return request.param * 2
326+
327+
@pytest.mark.parametrize('x, y', [('a', 'b')], indirect=True)
328+
def test_simple(x):
329+
assert len(x) == 3
330+
""")
331+
result = testdir.runpytest("--collect-only")
332+
result.stdout.fnmatch_lines([
333+
"*uses no fixture 'y'*",
334+
])
335+
336+
@pytest.mark.xfail
337+
@pytest.mark.issue714
338+
def test_parametrize_indirect_uses_no_fixture_error_indirect_list(self, testdir):
339+
testdir.makepyfile("""
340+
import pytest
341+
@pytest.fixture(scope='function')
342+
def x(request):
343+
return request.param * 3
344+
345+
@pytest.mark.parametrize('x, y', [('a', 'b')], indirect=['x'])
346+
def test_simple(x):
347+
assert len(x) == 3
348+
""")
349+
result = testdir.runpytest("--collect-only")
350+
result.stdout.fnmatch_lines([
351+
"*uses no fixture 'y'*",
352+
])
234353

235354
def test_addcalls_and_parametrize_indirect(self):
236355
def func(x, y): pass

0 commit comments

Comments
 (0)