parent
4414c4adae
commit
720c4585b5
1
AUTHORS
1
AUTHORS
|
@ -238,6 +238,7 @@ Michael Goerz
|
||||||
Michael Krebs
|
Michael Krebs
|
||||||
Michael Seifert
|
Michael Seifert
|
||||||
Michal Wajszczuk
|
Michal Wajszczuk
|
||||||
|
Michał Lowas-Rzechonek
|
||||||
Michał Zięba
|
Michał Zięba
|
||||||
Mihai Capotă
|
Mihai Capotă
|
||||||
Mike Hoyle (hoylemd)
|
Mike Hoyle (hoylemd)
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Add an optional ``indirect`` argument for ``pytest.fixture`` to make all
|
||||||
|
parametrizations of that fixture indirect by default.
|
|
@ -372,6 +372,24 @@ test:
|
||||||
This can be used, for example, to do more expensive setup at test run time in
|
This can be used, for example, to do more expensive setup at test run time in
|
||||||
the fixture, rather than having to run those setup steps at collection time.
|
the fixture, rather than having to run those setup steps at collection time.
|
||||||
|
|
||||||
|
It's also possible to configure this on the fixture level, making all tests
|
||||||
|
using that fixture indirectly parametrized by default:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(indirect=True)
|
||||||
|
def fixt(request):
|
||||||
|
return request.param * 3
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("fixt", ["a", "b"])
|
||||||
|
def test_indirect(fixt):
|
||||||
|
assert len(fixt) == 3
|
||||||
|
|
||||||
|
|
||||||
.. regendoc:wipe
|
.. regendoc:wipe
|
||||||
|
|
||||||
Apply indirect on particular arguments
|
Apply indirect on particular arguments
|
||||||
|
|
|
@ -212,6 +212,7 @@ def add_funcarg_pseudo_fixture_def(
|
||||||
func=get_direct_param_fixture_func,
|
func=get_direct_param_fixture_func,
|
||||||
scope=arg2scope[argname],
|
scope=arg2scope[argname],
|
||||||
params=valuelist,
|
params=valuelist,
|
||||||
|
indirect=False,
|
||||||
unittest=False,
|
unittest=False,
|
||||||
ids=None,
|
ids=None,
|
||||||
)
|
)
|
||||||
|
@ -943,6 +944,7 @@ class FixtureDef(Generic[FixtureValue]):
|
||||||
func: "_FixtureFunc[FixtureValue]",
|
func: "_FixtureFunc[FixtureValue]",
|
||||||
scope: Union[Scope, "_ScopeName", Callable[[str, Config], "_ScopeName"], None],
|
scope: Union[Scope, "_ScopeName", Callable[[str, Config], "_ScopeName"], None],
|
||||||
params: Optional[Sequence[object]],
|
params: Optional[Sequence[object]],
|
||||||
|
indirect: bool = False,
|
||||||
unittest: bool = False,
|
unittest: bool = False,
|
||||||
ids: Optional[
|
ids: Optional[
|
||||||
Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]]
|
Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]]
|
||||||
|
@ -987,6 +989,8 @@ class FixtureDef(Generic[FixtureValue]):
|
||||||
# assign to the parameter values, or a callable to generate an ID given
|
# assign to the parameter values, or a callable to generate an ID given
|
||||||
# a parameter value.
|
# a parameter value.
|
||||||
self.ids = ids
|
self.ids = ids
|
||||||
|
# Whether the fixture should be always indirectly parametrized
|
||||||
|
self.indirect = indirect
|
||||||
# The names requested by the fixtures.
|
# The names requested by the fixtures.
|
||||||
self.argnames = getfuncargnames(func, name=argname, is_method=unittest)
|
self.argnames = getfuncargnames(func, name=argname, is_method=unittest)
|
||||||
# Whether the fixture was collected from a unittest TestCase class.
|
# Whether the fixture was collected from a unittest TestCase class.
|
||||||
|
@ -1174,6 +1178,7 @@ class FixtureFunctionMarker:
|
||||||
converter=_ensure_immutable_ids,
|
converter=_ensure_immutable_ids,
|
||||||
)
|
)
|
||||||
name: Optional[str] = None
|
name: Optional[str] = None
|
||||||
|
indirect: bool = False
|
||||||
|
|
||||||
def __call__(self, function: FixtureFunction) -> FixtureFunction:
|
def __call__(self, function: FixtureFunction) -> FixtureFunction:
|
||||||
if inspect.isclass(function):
|
if inspect.isclass(function):
|
||||||
|
@ -1241,6 +1246,7 @@ def fixture(
|
||||||
Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]
|
Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]
|
||||||
] = None,
|
] = None,
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
|
indirect: bool = False,
|
||||||
) -> Union[FixtureFunctionMarker, FixtureFunction]:
|
) -> Union[FixtureFunctionMarker, FixtureFunction]:
|
||||||
"""Decorator to mark a fixture factory function.
|
"""Decorator to mark a fixture factory function.
|
||||||
|
|
||||||
|
@ -1298,6 +1304,7 @@ def fixture(
|
||||||
autouse=autouse,
|
autouse=autouse,
|
||||||
ids=ids,
|
ids=ids,
|
||||||
name=name,
|
name=name,
|
||||||
|
indirect=indirect,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Direct decoration.
|
# Direct decoration.
|
||||||
|
@ -1417,7 +1424,11 @@ class FixtureManager:
|
||||||
p_argnames, _ = ParameterSet._parse_parametrize_args(
|
p_argnames, _ = ParameterSet._parse_parametrize_args(
|
||||||
*marker.args, **marker.kwargs
|
*marker.args, **marker.kwargs
|
||||||
)
|
)
|
||||||
parametrize_argnames.extend(p_argnames)
|
for argname in p_argnames:
|
||||||
|
fixturedefs = self.getfixturedefs(argname, node.nodeid) or []
|
||||||
|
if any(f.indirect for f in fixturedefs):
|
||||||
|
continue
|
||||||
|
parametrize_argnames.append(argname)
|
||||||
|
|
||||||
return parametrize_argnames
|
return parametrize_argnames
|
||||||
|
|
||||||
|
@ -1614,10 +1625,14 @@ class FixtureManager:
|
||||||
func=obj,
|
func=obj,
|
||||||
scope=marker.scope,
|
scope=marker.scope,
|
||||||
params=marker.params,
|
params=marker.params,
|
||||||
|
indirect=marker.indirect,
|
||||||
unittest=unittest,
|
unittest=unittest,
|
||||||
ids=marker.ids,
|
ids=marker.ids,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if marker.indirect:
|
||||||
|
fixture_def.params = fixture_def.params or []
|
||||||
|
|
||||||
faclist = self._arg2fixturedefs.setdefault(name, [])
|
faclist = self._arg2fixturedefs.setdefault(name, [])
|
||||||
if fixture_def.has_location:
|
if fixture_def.has_location:
|
||||||
faclist.append(fixture_def)
|
faclist.append(fixture_def)
|
||||||
|
|
|
@ -1194,8 +1194,8 @@ class Metafunc:
|
||||||
#: Underlying Python test function.
|
#: Underlying Python test function.
|
||||||
self.function = definition.obj
|
self.function = definition.obj
|
||||||
|
|
||||||
#: Set of fixture names required by the test function.
|
#: Set of fixtures required by the test function
|
||||||
self.fixturenames = fixtureinfo.names_closure
|
self.fixtureinfo = fixtureinfo
|
||||||
|
|
||||||
#: Class object where the test function is defined in or ``None``.
|
#: Class object where the test function is defined in or ``None``.
|
||||||
self.cls = cls
|
self.cls = cls
|
||||||
|
@ -1205,6 +1205,10 @@ class Metafunc:
|
||||||
# Result of parametrize().
|
# Result of parametrize().
|
||||||
self._calls: List[CallSpec2] = []
|
self._calls: List[CallSpec2] = []
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fixturenames(self):
|
||||||
|
return self.fixtureinfo.names_closure
|
||||||
|
|
||||||
def parametrize(
|
def parametrize(
|
||||||
self,
|
self,
|
||||||
argnames: Union[str, List[str], Tuple[str, ...]],
|
argnames: Union[str, List[str], Tuple[str, ...]],
|
||||||
|
@ -1300,8 +1304,23 @@ class Metafunc:
|
||||||
else:
|
else:
|
||||||
scope_ = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect)
|
scope_ = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect)
|
||||||
|
|
||||||
|
if not isinstance(indirect, (bool, Sequence)):
|
||||||
|
fail(
|
||||||
|
"In {func}: expected Sequence or boolean for indirect, got {type}".format(
|
||||||
|
type=type(indirect).__name__, func=self.function.__name__
|
||||||
|
),
|
||||||
|
pytrace=False,
|
||||||
|
)
|
||||||
|
|
||||||
self._validate_if_using_arg_names(argnames, indirect)
|
self._validate_if_using_arg_names(argnames, indirect)
|
||||||
|
|
||||||
|
if indirect is not True: # False, or a list
|
||||||
|
indirect = list(indirect or [])
|
||||||
|
for argname in argnames:
|
||||||
|
fixturedefs = self.fixtureinfo.name2fixturedefs.get(argname, [])
|
||||||
|
if any(f.indirect for f in fixturedefs):
|
||||||
|
indirect.append(argname)
|
||||||
|
|
||||||
arg_values_types = self._resolve_arg_value_types(argnames, indirect)
|
arg_values_types = self._resolve_arg_value_types(argnames, indirect)
|
||||||
|
|
||||||
# Use any already (possibly) generated ids with parametrize Marks.
|
# Use any already (possibly) generated ids with parametrize Marks.
|
||||||
|
@ -1435,13 +1454,7 @@ class Metafunc:
|
||||||
pytrace=False,
|
pytrace=False,
|
||||||
)
|
)
|
||||||
valtypes[arg] = "params"
|
valtypes[arg] = "params"
|
||||||
else:
|
|
||||||
fail(
|
|
||||||
"In {func}: expected Sequence or boolean for indirect, got {type}".format(
|
|
||||||
type=type(indirect).__name__, func=self.function.__name__
|
|
||||||
),
|
|
||||||
pytrace=False,
|
|
||||||
)
|
|
||||||
return valtypes
|
return valtypes
|
||||||
|
|
||||||
def _validate_if_using_arg_names(
|
def _validate_if_using_arg_names(
|
||||||
|
|
|
@ -4468,3 +4468,105 @@ def test_yield_fixture_with_no_value(pytester: Pytester) -> None:
|
||||||
result.assert_outcomes(errors=1)
|
result.assert_outcomes(errors=1)
|
||||||
result.stdout.fnmatch_lines([expected])
|
result.stdout.fnmatch_lines([expected])
|
||||||
assert result.ret == ExitCode.TESTS_FAILED
|
assert result.ret == ExitCode.TESTS_FAILED
|
||||||
|
|
||||||
|
|
||||||
|
def test_fixture_indirect(pytester: Pytester) -> None:
|
||||||
|
pytester.makeconftest(
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
@pytest.fixture(indirect=True)
|
||||||
|
def indirect_sum(request):
|
||||||
|
return sum(request.param)
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def indirect_reversed(request):
|
||||||
|
return list(reversed(request.param))
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
pytester.makepyfile(
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
@pytest.mark.parametrize("indirect_sum, indirect_reversed, direct_list",
|
||||||
|
[
|
||||||
|
([1,2,3], [1,2,3], [1,2,3])
|
||||||
|
],
|
||||||
|
indirect=["indirect_reversed"]
|
||||||
|
)
|
||||||
|
def test_indirect_sum(indirect_sum, indirect_reversed, direct_list):
|
||||||
|
assert indirect_sum == 6
|
||||||
|
assert indirect_reversed == [3, 2, 1]
|
||||||
|
assert direct_list == [1, 2, 3]
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = pytester.runpytest()
|
||||||
|
result.assert_outcomes(passed=1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_fixture_indirect_always(pytester: Pytester) -> None:
|
||||||
|
pytester.makeconftest(
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
@pytest.fixture(indirect=True)
|
||||||
|
def indirect_sum(request):
|
||||||
|
return sum(request.param)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
pytester.makepyfile(
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
@pytest.mark.parametrize("indirect_sum",
|
||||||
|
[
|
||||||
|
([1,2,3])
|
||||||
|
],
|
||||||
|
indirect=[]
|
||||||
|
)
|
||||||
|
def test_indirect_sum(indirect_sum):
|
||||||
|
assert indirect_sum == 6
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = pytester.runpytest()
|
||||||
|
result.assert_outcomes(passed=1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_fixture_indirect_no_params(pytester: Pytester) -> None:
|
||||||
|
pytester.makeconftest(
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
@pytest.fixture(indirect=True)
|
||||||
|
def indirect_skip(request):
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
pytester.makepyfile(
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
def test_indirect_skip(indirect_skip):
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = pytester.runpytest()
|
||||||
|
result.assert_outcomes(skipped=1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_fixture_indirect_default_params(pytester: Pytester) -> None:
|
||||||
|
pytester.makeconftest(
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
@pytest.fixture(indirect=True, params=[1])
|
||||||
|
def indirect_default(request):
|
||||||
|
return request.param
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
pytester.makepyfile(
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
def test_indirect_sum(indirect_default):
|
||||||
|
assert indirect_default == 1
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = pytester.runpytest()
|
||||||
|
result.assert_outcomes(passed=1)
|
||||||
|
|
|
@ -22,6 +22,7 @@ from _pytest import python
|
||||||
from _pytest.compat import _format_args
|
from _pytest.compat import _format_args
|
||||||
from _pytest.compat import getfuncargnames
|
from _pytest.compat import getfuncargnames
|
||||||
from _pytest.compat import NOTSET
|
from _pytest.compat import NOTSET
|
||||||
|
from _pytest.fixtures import FixtureDef
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
from _pytest.pytester import Pytester
|
from _pytest.pytester import Pytester
|
||||||
from _pytest.python import IdMaker
|
from _pytest.python import IdMaker
|
||||||
|
@ -34,7 +35,7 @@ class TestMetafunc:
|
||||||
# on the funcarg level, so we don't need a full blown
|
# on the funcarg level, so we don't need a full blown
|
||||||
# initialization.
|
# initialization.
|
||||||
class FuncFixtureInfoMock:
|
class FuncFixtureInfoMock:
|
||||||
name2fixturedefs = None
|
name2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]] = {}
|
||||||
|
|
||||||
def __init__(self, names):
|
def __init__(self, names):
|
||||||
self.names_closure = names
|
self.names_closure = names
|
||||||
|
|
Loading…
Reference in New Issue