parent
4414c4adae
commit
720c4585b5
1
AUTHORS
1
AUTHORS
|
@ -238,6 +238,7 @@ Michael Goerz
|
|||
Michael Krebs
|
||||
Michael Seifert
|
||||
Michal Wajszczuk
|
||||
Michał Lowas-Rzechonek
|
||||
Michał Zięba
|
||||
Mihai Capotă
|
||||
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
|
||||
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
|
||||
|
||||
Apply indirect on particular arguments
|
||||
|
|
|
@ -212,6 +212,7 @@ def add_funcarg_pseudo_fixture_def(
|
|||
func=get_direct_param_fixture_func,
|
||||
scope=arg2scope[argname],
|
||||
params=valuelist,
|
||||
indirect=False,
|
||||
unittest=False,
|
||||
ids=None,
|
||||
)
|
||||
|
@ -943,6 +944,7 @@ class FixtureDef(Generic[FixtureValue]):
|
|||
func: "_FixtureFunc[FixtureValue]",
|
||||
scope: Union[Scope, "_ScopeName", Callable[[str, Config], "_ScopeName"], None],
|
||||
params: Optional[Sequence[object]],
|
||||
indirect: bool = False,
|
||||
unittest: bool = False,
|
||||
ids: Optional[
|
||||
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
|
||||
# a parameter value.
|
||||
self.ids = ids
|
||||
# Whether the fixture should be always indirectly parametrized
|
||||
self.indirect = indirect
|
||||
# The names requested by the fixtures.
|
||||
self.argnames = getfuncargnames(func, name=argname, is_method=unittest)
|
||||
# Whether the fixture was collected from a unittest TestCase class.
|
||||
|
@ -1174,6 +1178,7 @@ class FixtureFunctionMarker:
|
|||
converter=_ensure_immutable_ids,
|
||||
)
|
||||
name: Optional[str] = None
|
||||
indirect: bool = False
|
||||
|
||||
def __call__(self, function: FixtureFunction) -> FixtureFunction:
|
||||
if inspect.isclass(function):
|
||||
|
@ -1241,6 +1246,7 @@ def fixture(
|
|||
Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]
|
||||
] = None,
|
||||
name: Optional[str] = None,
|
||||
indirect: bool = False,
|
||||
) -> Union[FixtureFunctionMarker, FixtureFunction]:
|
||||
"""Decorator to mark a fixture factory function.
|
||||
|
||||
|
@ -1298,6 +1304,7 @@ def fixture(
|
|||
autouse=autouse,
|
||||
ids=ids,
|
||||
name=name,
|
||||
indirect=indirect,
|
||||
)
|
||||
|
||||
# Direct decoration.
|
||||
|
@ -1417,7 +1424,11 @@ class FixtureManager:
|
|||
p_argnames, _ = ParameterSet._parse_parametrize_args(
|
||||
*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
|
||||
|
||||
|
@ -1614,10 +1625,14 @@ class FixtureManager:
|
|||
func=obj,
|
||||
scope=marker.scope,
|
||||
params=marker.params,
|
||||
indirect=marker.indirect,
|
||||
unittest=unittest,
|
||||
ids=marker.ids,
|
||||
)
|
||||
|
||||
if marker.indirect:
|
||||
fixture_def.params = fixture_def.params or []
|
||||
|
||||
faclist = self._arg2fixturedefs.setdefault(name, [])
|
||||
if fixture_def.has_location:
|
||||
faclist.append(fixture_def)
|
||||
|
|
|
@ -1194,8 +1194,8 @@ class Metafunc:
|
|||
#: Underlying Python test function.
|
||||
self.function = definition.obj
|
||||
|
||||
#: Set of fixture names required by the test function.
|
||||
self.fixturenames = fixtureinfo.names_closure
|
||||
#: Set of fixtures required by the test function
|
||||
self.fixtureinfo = fixtureinfo
|
||||
|
||||
#: Class object where the test function is defined in or ``None``.
|
||||
self.cls = cls
|
||||
|
@ -1205,6 +1205,10 @@ class Metafunc:
|
|||
# Result of parametrize().
|
||||
self._calls: List[CallSpec2] = []
|
||||
|
||||
@property
|
||||
def fixturenames(self):
|
||||
return self.fixtureinfo.names_closure
|
||||
|
||||
def parametrize(
|
||||
self,
|
||||
argnames: Union[str, List[str], Tuple[str, ...]],
|
||||
|
@ -1300,8 +1304,23 @@ class Metafunc:
|
|||
else:
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
# Use any already (possibly) generated ids with parametrize Marks.
|
||||
|
@ -1435,13 +1454,7 @@ class Metafunc:
|
|||
pytrace=False,
|
||||
)
|
||||
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
|
||||
|
||||
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.stdout.fnmatch_lines([expected])
|
||||
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 getfuncargnames
|
||||
from _pytest.compat import NOTSET
|
||||
from _pytest.fixtures import FixtureDef
|
||||
from _pytest.outcomes import fail
|
||||
from _pytest.pytester import Pytester
|
||||
from _pytest.python import IdMaker
|
||||
|
@ -34,7 +35,7 @@ class TestMetafunc:
|
|||
# on the funcarg level, so we don't need a full blown
|
||||
# initialization.
|
||||
class FuncFixtureInfoMock:
|
||||
name2fixturedefs = None
|
||||
name2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]] = {}
|
||||
|
||||
def __init__(self, names):
|
||||
self.names_closure = names
|
||||
|
|
Loading…
Reference in New Issue