Skip to content

Commit b1c4c6b

Browse files
committed
Add versionadded, add a failure case
1 parent 5e01671 commit b1c4c6b

File tree

3 files changed

+68
-11
lines changed

3 files changed

+68
-11
lines changed

doc/en/example/parametrize.rst

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,8 @@ which is expected by pytest.
694694
Adding parameters depending on previous parametrizations
695695
--------------------------------------------------------------------
696696

697+
.. versionadded: 8.4
698+
697699
By default, :hook:`pytest_generate_tests` hooks and
698700
:ref:`pytest.mark.parametrize <pytest.mark.parametrize ref>` generate
699701
a Cartesian product of parameter sets in case of multiple parametrizations,
@@ -702,7 +704,8 @@ see :ref:`parametrize-basics` for some examples.
702704
Sometimes, values of some parameters need to be generated based on values
703705
of previous parameters or based on their associated marks.
704706

705-
In such cases ``parametrize`` can be passed a callable for ``argvalues``,
707+
In such cases :py:func:`Metafunc.parametrize <pytest.Metafunc.parametrize>`
708+
can be passed a callable for ``argvalues``,
706709
which will decide how to further parametrize each test instance:
707710

708711
.. code-block:: python
@@ -755,3 +758,10 @@ Running ``pytest`` with verbose mode outputs:
755758
test_parametrize_dependent.py::test_function[c-w] PASSED [100%]
756759
757760
============================ 6 passed in 0.12s =============================
761+
762+
In the example above, the callable form was used to ensure that all marks are
763+
accounted for.
764+
765+
Params from :hook:`pytest_generate_tests` hooks go after params from
766+
:ref:`pytest.mark.parametrize <pytest.mark.parametrize ref>` by default.
767+
:py:func:`pytest.hookimpl` can be used to affect the order of hooks.

src/_pytest/python.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1177,6 +1177,7 @@ def parametrize(
11771177
A comma-separated string denoting one or more argument names, or
11781178
a list/tuple of argument strings.
11791179
1180+
:type argvalues: Iterable[_pytest.mark.structures.ParameterSet | Sequence[object] | object] | Callable
11801181
:param argvalues:
11811182
The list of argvalues determines how often a test is invoked with
11821183
different argument values.
@@ -1188,9 +1189,9 @@ def parametrize(
11881189
:func:`pytest.param` can be used instead of tuples for additional
11891190
control over parameter sets.
11901191
1191-
Parameter sets can be generated depending on previous
1192-
parametrizations, see :ref:`parametrize_dependent`.
1193-
:type argvalues: Iterable[_pytest.mark.structures.ParameterSet | Sequence[object] | object] | Callable
1192+
.. versionadded: 8.4
1193+
``argvalues`` can be passed a callable,
1194+
see :ref:`parametrize_dependent`.
11941195
11951196
:param indirect:
11961197
A list of arguments' names (subset of argnames) or a boolean.

testing/python/metafunc.py

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2176,11 +2176,13 @@ def test_function(foo, bar):
21762176
]
21772177
)
21782178

2179-
def test_depend_on_marks(self, pytester: Pytester) -> None:
2179+
def test_hook_depends_on_marks(self, pytester: Pytester) -> None:
21802180
pytester.makepyfile(
21812181
"""
21822182
import pytest
21832183
2184+
# Note: without hookimpl, the hook goes after the parametrize mark.
2185+
@pytest.hookimpl(trylast=True)
21842186
def pytest_generate_tests(metafunc: pytest.Metafunc):
21852187
if "bar" in metafunc.fixturenames:
21862188
base_bar_marks = list(metafunc.definition.iter_markers("bar_params"))
@@ -2211,16 +2213,60 @@ def test_function(foo, bar):
22112213
result = pytester.runpytest("-vv", "-s")
22122214
result.stdout.fnmatch_lines(
22132215
[
2214-
"test_depend_on_marks.py::test_function[a-x] PASSED",
2215-
"test_depend_on_marks.py::test_function[b-x] PASSED",
2216-
"test_depend_on_marks.py::test_function[b-y] PASSED",
2217-
"test_depend_on_marks.py::test_function[b-z] PASSED",
2218-
"test_depend_on_marks.py::test_function[c-x] PASSED",
2219-
"test_depend_on_marks.py::test_function[c-w] PASSED",
2216+
"test_hook_depends_on_marks.py::test_function[a-x] PASSED",
2217+
"test_hook_depends_on_marks.py::test_function[b-x] PASSED",
2218+
"test_hook_depends_on_marks.py::test_function[b-y] PASSED",
2219+
"test_hook_depends_on_marks.py::test_function[b-z] PASSED",
2220+
"test_hook_depends_on_marks.py::test_function[c-x] PASSED",
2221+
"test_hook_depends_on_marks.py::test_function[c-w] PASSED",
22202222
"*= 6 passed in *",
22212223
]
22222224
)
22232225

2226+
@pytest.mark.skip(reason=":(")
2227+
def test_mark_depends_on_hooks(self, pytester: Pytester) -> None:
2228+
pytester.makepyfile(
2229+
"""
2230+
import pytest
2231+
2232+
# Note: with tryfirst, the hook goes before the parametrize mark.
2233+
@pytest.hookimpl(wrapper=True)
2234+
def pytest_generate_tests(metafunc: pytest.Metafunc):
2235+
if "foo" in metafunc.fixturenames:
2236+
metafunc.parametrize(
2237+
"foo",
2238+
[
2239+
pytest.param("a", marks=[pytest.mark.bar_params("x", "y")]),
2240+
pytest.param("b", marks=[pytest.mark.bar_params("z")]),
2241+
],
2242+
)
2243+
return (yield)
2244+
2245+
2246+
def gen_params(callspec: pytest.CallSpec):
2247+
bar_marks = [
2248+
mark
2249+
for mark in callspec.marks
2250+
if mark.name == "bar_params"
2251+
]
2252+
return [arg for mark in bar_marks for arg in mark.args]
2253+
2254+
2255+
@pytest.mark.parametrize("bar", gen_params)
2256+
def test_function(foo, bar):
2257+
pass
2258+
"""
2259+
)
2260+
result = pytester.runpytest("-vv", "-s")
2261+
result.stdout.fnmatch_lines(
2262+
[
2263+
"test_mark_depends_on_hooks.py::test_function[a-x] PASSED",
2264+
"test_mark_depends_on_hooks.py::test_function[a-y] PASSED",
2265+
"test_mark_depends_on_hooks.py::test_function[b-z] PASSED",
2266+
"*= 3 passed in *",
2267+
]
2268+
)
2269+
22242270
def test_id_and_marks(self, pytester: Pytester) -> None:
22252271
pytester.makepyfile(
22262272
"""

0 commit comments

Comments
 (0)