Slight change in early teardown solution
Fix some bugs and do some improvements and refactors
This commit is contained in:
parent
6fc6b153fd
commit
5749ea2f46
|
@ -225,13 +225,19 @@ class FixtureArgKey:
|
|||
param_value: Optional[Hashable]
|
||||
scoped_item_path: Optional[Path]
|
||||
item_cls: Optional[type]
|
||||
baseid: Optional[str]
|
||||
|
||||
|
||||
def get_fixture_arg_key(item: nodes.Item, argname: str, scope: Scope) -> FixtureArgKey:
|
||||
def get_fixture_arg_key(
|
||||
item: nodes.Item,
|
||||
argname: str,
|
||||
scope: Scope,
|
||||
baseid: Optional[str] = None,
|
||||
is_parametrized: Optional[bool] = False,
|
||||
) -> FixtureArgKey:
|
||||
param_index = None
|
||||
param_value = None
|
||||
if hasattr(item, "callspec") and argname in item.callspec.params:
|
||||
# Fixture is parametrized.
|
||||
if is_parametrized:
|
||||
if isinstance(item.callspec.params[argname], Hashable):
|
||||
param_value = item.callspec.params[argname]
|
||||
else:
|
||||
|
@ -251,7 +257,9 @@ def get_fixture_arg_key(item: nodes.Item, argname: str, scope: Scope) -> Fixture
|
|||
else:
|
||||
item_cls = None
|
||||
|
||||
return FixtureArgKey(argname, param_index, param_value, scoped_item_path, item_cls)
|
||||
return FixtureArgKey(
|
||||
argname, param_index, param_value, scoped_item_path, item_cls, baseid
|
||||
)
|
||||
|
||||
|
||||
def get_fixture_keys(item: nodes.Item, scope: Scope) -> Iterator[FixtureArgKey]:
|
||||
|
@ -259,17 +267,27 @@ def get_fixture_keys(item: nodes.Item, scope: Scope) -> Iterator[FixtureArgKey]:
|
|||
the specified scope."""
|
||||
assert scope is not Scope.Function
|
||||
if hasattr(item, "_fixtureinfo"):
|
||||
# sort this so that different calls to
|
||||
# get_fixture_keys will be deterministic.
|
||||
for argname, fixture_def in sorted(item._fixtureinfo.name2fixturedefs.items()):
|
||||
for argname in item._fixtureinfo.names_closure:
|
||||
is_parametrized = (
|
||||
hasattr(item, "callspec") and argname in item.callspec._arg2scope
|
||||
)
|
||||
for i in reversed(
|
||||
range(item._fixtureinfo.name2num_fixturedefs_used[argname])
|
||||
):
|
||||
fixturedef = item._fixtureinfo.name2fixturedefs[argname][-(i + 1)]
|
||||
# In the case item is parametrized on the `argname` with
|
||||
# a scope, it overrides that of the fixture.
|
||||
if hasattr(item, "callspec") and argname in item.callspec._arg2scope:
|
||||
if item.callspec._arg2scope[argname] != scope:
|
||||
continue
|
||||
elif fixture_def[-1]._scope != scope:
|
||||
continue
|
||||
yield get_fixture_arg_key(item, argname, scope)
|
||||
if (is_parametrized and item.callspec._arg2scope[argname] != scope) or (
|
||||
not is_parametrized and fixturedef._scope != scope
|
||||
):
|
||||
break
|
||||
yield get_fixture_arg_key(
|
||||
item,
|
||||
argname,
|
||||
scope,
|
||||
None if fixturedef.is_pseudo else fixturedef.baseid,
|
||||
is_parametrized,
|
||||
)
|
||||
|
||||
|
||||
# Algorithm for sorting on a per-parametrized resource setup basis.
|
||||
|
@ -293,17 +311,31 @@ def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]:
|
|||
for key in keys:
|
||||
item_d[key].append(item)
|
||||
items_dict = dict.fromkeys(items, None)
|
||||
last_item_by_argkey: Dict[FixtureArgKey, nodes.Item] = {}
|
||||
reordered_items = list(
|
||||
reorder_items_atscope(items_dict, argkeys_cache, items_by_argkey, Scope.Session)
|
||||
reorder_items_atscope(
|
||||
items_dict,
|
||||
argkeys_cache,
|
||||
items_by_argkey,
|
||||
last_item_by_argkey,
|
||||
Scope.Session,
|
||||
)
|
||||
)
|
||||
for scope in reversed(HIGH_SCOPES):
|
||||
for key in items_by_argkey[scope]:
|
||||
last_item_dependent_on_key = items_by_argkey[scope][key].pop()
|
||||
last_item_dependent_on_key = last_item_by_argkey[key]
|
||||
if key.baseid is None:
|
||||
continue
|
||||
for i in range(
|
||||
last_item_dependent_on_key._fixtureinfo.name2num_fixturedefs_used[
|
||||
key.argname
|
||||
]
|
||||
):
|
||||
fixturedef = last_item_dependent_on_key._fixtureinfo.name2fixturedefs[
|
||||
key.argname
|
||||
][-1]
|
||||
if fixturedef.is_pseudo:
|
||||
continue
|
||||
][-(i + 1)]
|
||||
if fixturedef.baseid == key.baseid:
|
||||
break
|
||||
last_item_dependent_on_key.teardown = functools.partial(
|
||||
lambda other_finalizers, new_finalizer: [
|
||||
finalizer() for finalizer in (new_finalizer, other_finalizers)
|
||||
|
@ -330,20 +362,28 @@ def fix_cache_order(
|
|||
if key in ignore:
|
||||
continue
|
||||
items_by_argkey[scope][key].appendleft(item)
|
||||
# Make sure last dependent item on a key
|
||||
# remains updated while reordering.
|
||||
if items_by_argkey[scope][key][-1] == item:
|
||||
items_by_argkey[scope][key].pop()
|
||||
|
||||
|
||||
def reorder_items_atscope(
|
||||
items: Dict[nodes.Item, None],
|
||||
argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]],
|
||||
items_by_argkey: Dict[Scope, Dict[FixtureArgKey, "Deque[nodes.Item]"]],
|
||||
last_item_by_argkey: Dict[FixtureArgKey, nodes.Item],
|
||||
scope: Scope,
|
||||
) -> Dict[nodes.Item, None]:
|
||||
if scope is Scope.Function or len(items) < 3:
|
||||
if scope is Scope.Function:
|
||||
return items
|
||||
elif len(items) < 3:
|
||||
for item in items:
|
||||
for key in argkeys_cache[scope].get(item, []):
|
||||
last_item_by_argkey[key] = item
|
||||
return reorder_items_atscope(
|
||||
items,
|
||||
argkeys_cache,
|
||||
items_by_argkey,
|
||||
last_item_by_argkey,
|
||||
scope.next_lower(),
|
||||
)
|
||||
ignore: Set[Optional[FixtureArgKey]] = set()
|
||||
items_deque = deque(items)
|
||||
items_done: Dict[nodes.Item, None] = {}
|
||||
|
@ -363,20 +403,26 @@ def reorder_items_atscope(
|
|||
no_argkey_group[item] = None
|
||||
else:
|
||||
slicing_argkey, _ = argkeys.popitem()
|
||||
# We don't have to remove relevant items from later in the
|
||||
# deque because they'll just be ignored.
|
||||
matching_items = [
|
||||
i for i in scoped_items_by_argkey[slicing_argkey] if i in items
|
||||
]
|
||||
for i in reversed(matching_items):
|
||||
for i in reversed(
|
||||
dict.fromkeys(scoped_items_by_argkey[slicing_argkey])
|
||||
):
|
||||
if i not in items:
|
||||
continue
|
||||
fix_cache_order(i, argkeys_cache, items_by_argkey, ignore, scope)
|
||||
items_deque.appendleft(i)
|
||||
break
|
||||
if no_argkey_group:
|
||||
no_argkey_group = reorder_items_atscope(
|
||||
no_argkey_group, argkeys_cache, items_by_argkey, scope.next_lower()
|
||||
no_argkey_group,
|
||||
argkeys_cache,
|
||||
items_by_argkey,
|
||||
last_item_by_argkey,
|
||||
scope.next_lower(),
|
||||
)
|
||||
for item in no_argkey_group:
|
||||
for key in scoped_argkeys_cache.get(item, []):
|
||||
last_item_by_argkey[key] = item
|
||||
items_done[item] = None
|
||||
ignore.add(slicing_argkey)
|
||||
return items_done
|
||||
|
@ -384,7 +430,13 @@ def reorder_items_atscope(
|
|||
|
||||
@dataclasses.dataclass
|
||||
class FuncFixtureInfo:
|
||||
__slots__ = ("argnames", "initialnames", "names_closure", "name2fixturedefs")
|
||||
__slots__ = (
|
||||
"argnames",
|
||||
"initialnames",
|
||||
"names_closure",
|
||||
"name2fixturedefs",
|
||||
"name2num_fixturedefs_used",
|
||||
)
|
||||
|
||||
# Original function argument names.
|
||||
argnames: Tuple[str, ...]
|
||||
|
@ -394,6 +446,7 @@ class FuncFixtureInfo:
|
|||
initialnames: Tuple[str, ...]
|
||||
names_closure: List[str]
|
||||
name2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]]
|
||||
name2num_fixturedefs_used: Dict[str, int]
|
||||
|
||||
def prune_dependency_tree(self) -> None:
|
||||
"""Recompute names_closure from initialnames and name2fixturedefs.
|
||||
|
@ -401,26 +454,11 @@ class FuncFixtureInfo:
|
|||
Can only reduce names_closure, which means that the new closure will
|
||||
always be a subset of the old one. The order is preserved.
|
||||
|
||||
This method is needed because direct parametrization may shadow some
|
||||
of the fixtures that were included in the originally built dependency
|
||||
This method is needed because dynamic direct parametrization may shadow
|
||||
some of the fixtures that were included in the originally built dependency
|
||||
tree. In this way the dependency tree can get pruned, and the closure
|
||||
of argnames may get reduced.
|
||||
"""
|
||||
closure: Set[str] = set()
|
||||
working_set = set(self.initialnames)
|
||||
while working_set:
|
||||
argname = working_set.pop()
|
||||
# Argname may be smth not included in the original names_closure,
|
||||
# in which case we ignore it. This currently happens with pseudo
|
||||
# FixtureDefs which wrap 'get_direct_param_fixture_func(request)'.
|
||||
# So they introduce the new dependency 'request' which might have
|
||||
# been missing in the original tree (closure).
|
||||
if argname not in closure and argname in self.names_closure:
|
||||
closure.add(argname)
|
||||
if argname in self.name2fixturedefs:
|
||||
working_set.update(self.name2fixturedefs[argname][-1].argnames)
|
||||
|
||||
self.names_closure[:] = sorted(closure, key=self.names_closure.index)
|
||||
|
||||
|
||||
class FixtureRequest:
|
||||
|
@ -1407,6 +1445,26 @@ def pytest_addoption(parser: Parser) -> None:
|
|||
)
|
||||
|
||||
|
||||
def _get_direct_parametrize_args(node: nodes.Node) -> List[str]:
|
||||
"""Return all direct parametrization arguments of a node, so we don't
|
||||
mistake them for fixtures.
|
||||
|
||||
Check https://github.com/pytest-dev/pytest/issues/5036.
|
||||
|
||||
These things are done later as well when dealing with parametrization
|
||||
so this could be improved.
|
||||
"""
|
||||
parametrize_argnames: List[str] = []
|
||||
for marker in node.iter_markers(name="parametrize"):
|
||||
if not marker.kwargs.get("indirect", False):
|
||||
p_argnames, _ = ParameterSet._parse_parametrize_args(
|
||||
*marker.args, **marker.kwargs
|
||||
)
|
||||
parametrize_argnames.extend(p_argnames)
|
||||
|
||||
return parametrize_argnames
|
||||
|
||||
|
||||
class FixtureManager:
|
||||
"""pytest fixture definitions and information is stored and managed
|
||||
from this class.
|
||||
|
@ -1452,25 +1510,6 @@ class FixtureManager:
|
|||
}
|
||||
session.config.pluginmanager.register(self, "funcmanage")
|
||||
|
||||
def _get_direct_parametrize_args(self, node: nodes.Node) -> List[str]:
|
||||
"""Return all direct parametrization arguments of a node, so we don't
|
||||
mistake them for fixtures.
|
||||
|
||||
Check https://github.com/pytest-dev/pytest/issues/5036.
|
||||
|
||||
These things are done later as well when dealing with parametrization
|
||||
so this could be improved.
|
||||
"""
|
||||
parametrize_argnames: List[str] = []
|
||||
for marker in node.iter_markers(name="parametrize"):
|
||||
if not marker.kwargs.get("indirect", False):
|
||||
p_argnames, _ = ParameterSet._parse_parametrize_args(
|
||||
*marker.args, **marker.kwargs
|
||||
)
|
||||
parametrize_argnames.extend(p_argnames)
|
||||
|
||||
return parametrize_argnames
|
||||
|
||||
def getfixtureinfo(
|
||||
self, node: nodes.Node, func, cls, funcargs: bool = True
|
||||
) -> FuncFixtureInfo:
|
||||
|
@ -1482,12 +1521,27 @@ class FixtureManager:
|
|||
usefixtures = tuple(
|
||||
arg for mark in node.iter_markers(name="usefixtures") for arg in mark.args
|
||||
)
|
||||
initialnames = usefixtures + argnames
|
||||
fm = node.session._fixturemanager
|
||||
initialnames, names_closure, arg2fixturedefs = fm.getfixtureclosure(
|
||||
initialnames, node, ignore_args=self._get_direct_parametrize_args(node)
|
||||
initialnames = tuple(
|
||||
dict.fromkeys(
|
||||
tuple(self._getautousenames(node.nodeid)) + usefixtures + argnames
|
||||
)
|
||||
)
|
||||
|
||||
arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]] = {}
|
||||
names_closure, arg2num_fixturedefs_used = self.getfixtureclosure(
|
||||
node,
|
||||
initialnames,
|
||||
arg2fixturedefs,
|
||||
self,
|
||||
ignore_args=_get_direct_parametrize_args(node),
|
||||
)
|
||||
return FuncFixtureInfo(
|
||||
argnames,
|
||||
initialnames,
|
||||
names_closure,
|
||||
arg2fixturedefs,
|
||||
arg2num_fixturedefs_used,
|
||||
)
|
||||
return FuncFixtureInfo(argnames, initialnames, names_closure, arg2fixturedefs)
|
||||
|
||||
def pytest_plugin_registered(self, plugin: _PluggyPlugin) -> None:
|
||||
nodeid = None
|
||||
|
@ -1518,12 +1572,14 @@ class FixtureManager:
|
|||
if basenames:
|
||||
yield from basenames
|
||||
|
||||
@staticmethod
|
||||
def getfixtureclosure(
|
||||
self,
|
||||
fixturenames: Tuple[str, ...],
|
||||
parentnode: nodes.Node,
|
||||
initialnames: Tuple[str],
|
||||
arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]],
|
||||
fixturemanager: Optional["FixtureManager"] = None,
|
||||
ignore_args: Sequence[str] = (),
|
||||
) -> Tuple[Tuple[str, ...], List[str], Dict[str, Sequence[FixtureDef[Any]]]]:
|
||||
) -> Tuple[List[str], Dict[str, List[FixtureDef[Any]]]]:
|
||||
# Collect the closure of all fixtures, starting with the given
|
||||
# fixturenames as the initial set. As we have to visit all
|
||||
# factory definitions anyway, we also return an arg2fixturedefs
|
||||
|
@ -1532,44 +1588,74 @@ class FixtureManager:
|
|||
# (discovering matching fixtures for a given name/node is expensive).
|
||||
|
||||
parentid = parentnode.nodeid
|
||||
fixturenames_closure = list(self._getautousenames(parentid))
|
||||
|
||||
def merge(otherlist: Iterable[str]) -> None:
|
||||
for arg in otherlist:
|
||||
if arg not in fixturenames_closure:
|
||||
fixturenames_closure.append(arg)
|
||||
|
||||
merge(fixturenames)
|
||||
fixturenames_closure: Dict[str, int] = {}
|
||||
|
||||
# At this point, fixturenames_closure contains what we call "initialnames",
|
||||
# which is a set of fixturenames the function immediately requests. We
|
||||
# need to return it as well, so save this.
|
||||
initialnames = tuple(fixturenames_closure)
|
||||
|
||||
arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]] = {}
|
||||
lastlen = -1
|
||||
while lastlen != len(fixturenames_closure):
|
||||
lastlen = len(fixturenames_closure)
|
||||
for argname in fixturenames_closure:
|
||||
if argname in ignore_args:
|
||||
continue
|
||||
if argname in arg2fixturedefs:
|
||||
continue
|
||||
fixturedefs = self.getfixturedefs(argname, parentid)
|
||||
arg2num_fixturedefs_used: Dict[str, int] = defaultdict(lambda: 0)
|
||||
arg2num_def_used_in_path: Dict[str, int] = defaultdict(lambda: 0)
|
||||
nodes_in_fixture_tree: Deque[Tuple[str, bool]] = deque(
|
||||
[(name, name != initialnames[-1]) for name in initialnames]
|
||||
)
|
||||
nodes_in_path: Deque[Tuple[str, bool]] = deque()
|
||||
|
||||
while nodes_in_fixture_tree:
|
||||
node, has_sibling = nodes_in_fixture_tree.popleft()
|
||||
if node not in fixturenames_closure or fixturenames_closure[node][0] > len(
|
||||
nodes_in_path
|
||||
):
|
||||
fixturenames_closure[node] = (
|
||||
len(nodes_in_path),
|
||||
len(fixturenames_closure),
|
||||
)
|
||||
if node not in arg2fixturedefs:
|
||||
if node not in ignore_args:
|
||||
fixturedefs = fixturemanager.getfixturedefs(node, parentid)
|
||||
if fixturedefs:
|
||||
arg2fixturedefs[argname] = fixturedefs
|
||||
merge(fixturedefs[-1].argnames)
|
||||
arg2fixturedefs[node] = fixturedefs
|
||||
if node in arg2fixturedefs:
|
||||
def_index = arg2num_def_used_in_path[node] + 1
|
||||
if (
|
||||
def_index <= len(arg2fixturedefs[node])
|
||||
and def_index > arg2num_fixturedefs_used[node]
|
||||
):
|
||||
arg2num_fixturedefs_used[node] = def_index
|
||||
fixturedef = arg2fixturedefs[node][-def_index]
|
||||
if fixturedef.argnames:
|
||||
nodes_in_path.append((node, has_sibling))
|
||||
arg2num_def_used_in_path[node] += 1
|
||||
nodes_in_fixture_tree.extendleft(
|
||||
[
|
||||
(argname, argname != fixturedef.argnames[-1])
|
||||
for argname in reversed(fixturedef.argnames)
|
||||
]
|
||||
)
|
||||
continue
|
||||
while not has_sibling:
|
||||
try:
|
||||
node, has_sibling = nodes_in_path.pop()
|
||||
except IndexError:
|
||||
assert len(nodes_in_fixture_tree) == 0
|
||||
break
|
||||
arg2num_def_used_in_path[node] -= 1
|
||||
|
||||
def sort_by_scope(arg_name: str) -> Scope:
|
||||
fixturenames_closure_list = list(fixturenames_closure)
|
||||
|
||||
def sort_by_scope_depth_and_arrival(arg_name: str) -> Scope:
|
||||
depth, arrival = fixturenames_closure[arg_name]
|
||||
try:
|
||||
fixturedefs = arg2fixturedefs[arg_name]
|
||||
except KeyError:
|
||||
return Scope.Function
|
||||
return (Scope.Function, -depth, -arrival)
|
||||
else:
|
||||
return fixturedefs[-1]._scope
|
||||
return (fixturedefs[-1]._scope, -depth, -arrival)
|
||||
|
||||
fixturenames_closure.sort(key=sort_by_scope, reverse=True)
|
||||
return initialnames, fixturenames_closure, arg2fixturedefs
|
||||
fixturenames_closure_list.sort(
|
||||
key=sort_by_scope_depth_and_arrival, reverse=True
|
||||
)
|
||||
return fixturenames_closure_list, arg2num_fixturedefs_used
|
||||
|
||||
def pytest_generate_tests(self, metafunc: "Metafunc") -> None:
|
||||
"""Generate new tests based on parametrized fixtures used by the given metafunc"""
|
||||
|
@ -1744,6 +1830,13 @@ class FixtureManager:
|
|||
def _matchfactories(
|
||||
self, fixturedefs: Iterable[FixtureDef[Any]], nodeid: str
|
||||
) -> Iterator[FixtureDef[Any]]:
|
||||
"""Yields the visible fixturedefs to a node with the given id
|
||||
from among the specified fixturedefs.
|
||||
|
||||
:param Iterable[FixtureDef] fixturedefs: The list of specified fixturedefs.
|
||||
:param str nodeid: Full node id of the node.
|
||||
:rtype: Iterator[FixtureDef]
|
||||
"""
|
||||
parentnodeids = set(nodes.iterparentnodeids(nodeid))
|
||||
for fixturedef in fixturedefs:
|
||||
if fixturedef.baseid in parentnodeids:
|
||||
|
|
|
@ -58,7 +58,9 @@ from _pytest.config.argparsing import Parser
|
|||
from _pytest.deprecated import check_ispytest
|
||||
from _pytest.deprecated import INSTANCE_COLLECTOR
|
||||
from _pytest.deprecated import NOSE_SUPPORT_METHOD
|
||||
from _pytest.fixtures import _get_direct_parametrize_args
|
||||
from _pytest.fixtures import FixtureDef
|
||||
from _pytest.fixtures import FixtureManager
|
||||
from _pytest.fixtures import FixtureRequest
|
||||
from _pytest.fixtures import FuncFixtureInfo
|
||||
from _pytest.fixtures import get_scope_node
|
||||
|
@ -388,6 +390,26 @@ del _EmptyClass
|
|||
# fmt: on
|
||||
|
||||
|
||||
def unwrap_metafunc_parametrize_and_possibly_prune_dependency_tree(metafunc):
|
||||
metafunc.parametrize = metafunc._parametrize
|
||||
del metafunc._parametrize
|
||||
if metafunc.has_dynamic_parametrize:
|
||||
# Direct parametrization may have shadowed some fixtures
|
||||
# so make sure we update what the function really needs.
|
||||
fixture_closure, arg2num_fixturedefs_used = FixtureManager.getfixtureclosure(
|
||||
metafunc.definition,
|
||||
metafunc.definition._fixtureinfo.initialnames,
|
||||
metafunc._arg2fixturedefs,
|
||||
ignore_args=_get_direct_parametrize_args(metafunc.definition) + ["request"],
|
||||
)
|
||||
metafunc.fixturenames[:] = fixture_closure
|
||||
metafunc.definition._fixtureinfo.name2num_fixturedefs_used.clear()
|
||||
metafunc.definition._fixtureinfo.name2num_fixturedefs_used.update(
|
||||
arg2num_fixturedefs_used
|
||||
)
|
||||
del metafunc.has_dynamic_parametrize
|
||||
|
||||
|
||||
class PyCollector(PyobjMixin, nodes.Collector):
|
||||
def funcnamefilter(self, name: str) -> bool:
|
||||
return self._matches_prefix_or_glob_option("python_functions", name)
|
||||
|
@ -484,8 +506,6 @@ class PyCollector(PyobjMixin, nodes.Collector):
|
|||
definition = FunctionDefinition.from_parent(self, name=name, callobj=funcobj)
|
||||
fixtureinfo = definition._fixtureinfo
|
||||
|
||||
# pytest_generate_tests impls call metafunc.parametrize() which fills
|
||||
# metafunc._calls, the outcome of the hook.
|
||||
metafunc = Metafunc(
|
||||
definition=definition,
|
||||
fixtureinfo=fixtureinfo,
|
||||
|
@ -494,19 +514,29 @@ class PyCollector(PyobjMixin, nodes.Collector):
|
|||
module=module,
|
||||
_ispytest=True,
|
||||
)
|
||||
methods = []
|
||||
methods = [unwrap_metafunc_parametrize_and_possibly_prune_dependency_tree]
|
||||
if hasattr(module, "pytest_generate_tests"):
|
||||
methods.append(module.pytest_generate_tests)
|
||||
if cls is not None and hasattr(cls, "pytest_generate_tests"):
|
||||
methods.append(cls().pytest_generate_tests)
|
||||
metafunc.has_dynamic_parametrize = False
|
||||
from functools import wraps
|
||||
|
||||
@wraps(metafunc.parametrize)
|
||||
def set_has_dynamic_parametrize(*args, **kwargs):
|
||||
metafunc.has_dynamic_parametrize = True
|
||||
metafunc._parametrize(*args, **kwargs)
|
||||
|
||||
metafunc._parametrize = metafunc.parametrize
|
||||
metafunc.parametrize = set_has_dynamic_parametrize
|
||||
|
||||
# pytest_generate_tests impls call metafunc.parametrize() which fills
|
||||
# metafunc._calls, the outcome of the hook.
|
||||
self.ihook.pytest_generate_tests.call_extra(methods, dict(metafunc=metafunc))
|
||||
|
||||
if not metafunc._calls:
|
||||
yield Function.from_parent(self, name=name, fixtureinfo=fixtureinfo)
|
||||
else:
|
||||
# Direct parametrization may have shadowed some fixtures
|
||||
# so make sure we update what the function really needs.
|
||||
fixtureinfo.prune_dependency_tree()
|
||||
|
||||
for callspec in metafunc._calls:
|
||||
subname = f"{name}[{callspec.id}]"
|
||||
yield Function.from_parent(
|
||||
|
@ -1360,7 +1390,7 @@ class Metafunc:
|
|||
if arg_values_types[argname] == "params":
|
||||
continue
|
||||
if name2pseudofixturedef is not None and argname in name2pseudofixturedef:
|
||||
arg2fixturedefs[argname] = [name2pseudofixturedef[argname]]
|
||||
fixturedef = name2pseudofixturedef[argname]
|
||||
else:
|
||||
fixturedef = FixtureDef(
|
||||
fixturemanager=self.definition.session._fixturemanager,
|
||||
|
@ -1373,9 +1403,10 @@ class Metafunc:
|
|||
ids=None,
|
||||
is_pseudo=True,
|
||||
)
|
||||
arg2fixturedefs[argname] = [fixturedef]
|
||||
if name2pseudofixturedef is not None:
|
||||
name2pseudofixturedef[argname] = fixturedef
|
||||
arg2fixturedefs[argname] = [fixturedef]
|
||||
self.definition._fixtureinfo.name2num_fixturedefs_used[argname] = 1
|
||||
|
||||
# Create the new calls: if we are parametrize() multiple times (by applying the decorator
|
||||
# more than once) then we accumulate those calls generating the cartesian product
|
||||
|
@ -1557,7 +1588,7 @@ def _find_parametrized_scope(
|
|||
if all_arguments_are_fixtures:
|
||||
fixturedefs = arg2fixturedefs or {}
|
||||
used_scopes = [
|
||||
fixturedef[0]._scope
|
||||
fixturedef[0]._scope # Shouldn't be -1 ?
|
||||
for name, fixturedef in fixturedefs.items()
|
||||
if name in argnames
|
||||
]
|
||||
|
|
|
@ -2,6 +2,7 @@ import os
|
|||
import sys
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
from _pytest import fixtures
|
||||
|
@ -2681,16 +2682,19 @@ class TestFixtureMarker:
|
|||
"""
|
||||
)
|
||||
result = pytester.runpytest("-v")
|
||||
# Order changed because fixture keys were sorted by their names in fixtures::get_fixture_keys
|
||||
# beforehand so encap key came before flavor. This isn't problematic here as both fixtures
|
||||
# are session-scoped but when this isn't the case, it might be problematic.
|
||||
result.stdout.fnmatch_lines(
|
||||
"""
|
||||
test_dynamic_parametrized_ordering.py::test[flavor1-vxlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test2[flavor1-vxlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test[flavor2-vxlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test2[flavor2-vxlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test[flavor2-vlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test2[flavor2-vlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test[flavor1-vlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test2[flavor1-vlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test[flavor2-vlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test2[flavor2-vlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test[flavor2-vxlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test2[flavor2-vxlan] PASSED
|
||||
"""
|
||||
)
|
||||
|
||||
|
@ -4475,39 +4479,39 @@ def test_yield_fixture_with_no_value(pytester: Pytester) -> None:
|
|||
assert result.ret == ExitCode.TESTS_FAILED
|
||||
|
||||
|
||||
def test_teardown_high_scope_fixture_at_last_dependent_item_simple(
|
||||
def test_early_teardown_simple(
|
||||
pytester: Pytester,
|
||||
) -> None:
|
||||
pytester.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
@pytest.fixture(scope='module', params=[None])
|
||||
@pytest.fixture(scope='module')
|
||||
def fixture():
|
||||
yield
|
||||
print("Tearing down fixture!")
|
||||
pass
|
||||
|
||||
def test_0(fixture):
|
||||
pass
|
||||
|
||||
def test_1(fixture):
|
||||
print("Running test_1!")
|
||||
pass
|
||||
|
||||
def test_2():
|
||||
print("Running test_2!")
|
||||
pass
|
||||
"""
|
||||
)
|
||||
result = pytester.runpytest("-s")
|
||||
assert result.ret == 0
|
||||
result = pytester.runpytest("--setup-show")
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*Running test_1!*",
|
||||
"*Tearing down fixture!*",
|
||||
"*Running test_2!*",
|
||||
"*SETUP M fixture*",
|
||||
"*test_0*",
|
||||
"*test_1*",
|
||||
"*TEARDOWN M fixture*",
|
||||
"*test_2*",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_teardown_high_scope_fixture_at_last_dependent_item_simple_2(
|
||||
def test_early_teardown_simple_2(
|
||||
pytester: Pytester,
|
||||
) -> None:
|
||||
pytester.makepyfile(
|
||||
|
@ -4515,37 +4519,75 @@ def test_teardown_high_scope_fixture_at_last_dependent_item_simple_2(
|
|||
import pytest
|
||||
@pytest.fixture(scope='module', params=[None])
|
||||
def fixture1():
|
||||
yield
|
||||
print("Tearing down fixture!")
|
||||
pass
|
||||
|
||||
@pytest.fixture(scope='module', params=[None])
|
||||
def fixture2():
|
||||
yield
|
||||
print("Tearing down fixture!")
|
||||
pass
|
||||
|
||||
def test_0(fixture1):
|
||||
pass
|
||||
|
||||
def test_1(fixture1, fixture2):
|
||||
print("Running test_1!")
|
||||
pass
|
||||
|
||||
def test_2():
|
||||
print("Running test_2!")
|
||||
pass
|
||||
"""
|
||||
)
|
||||
result = pytester.runpytest("-s")
|
||||
result = pytester.runpytest("--setup-show")
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*Running test_1!*",
|
||||
"*Tearing down fixture!*",
|
||||
"*Tearing down fixture!*",
|
||||
"*Running test_2!*",
|
||||
"*SETUP M fixture1*",
|
||||
"*test_0*",
|
||||
"*SETUP M fixture2*",
|
||||
"*test_1*",
|
||||
"*TEARDOWN M fixture2*",
|
||||
"*TEARDOWN M fixture1*",
|
||||
"*test_2*",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_teardown_high_scope_fixture_at_last_dependent_item_complex(
|
||||
def test_early_teardown_simple_3(pytester: Pytester):
|
||||
pytester.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
@pytest.fixture(scope='module')
|
||||
def fixture1():
|
||||
pass
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def fixture2():
|
||||
pass
|
||||
|
||||
def test_0(fixture1, fixture2):
|
||||
pass
|
||||
|
||||
def test_1(fixture1):
|
||||
pass
|
||||
|
||||
def test_2(fixture1, fixture2):
|
||||
pass
|
||||
"""
|
||||
)
|
||||
result = pytester.runpytest("--setup-show")
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*SETUP M fixture1*",
|
||||
"*SETUP M fixture2*",
|
||||
"*test_0*",
|
||||
"*test_2*",
|
||||
"*TEARDOWN M fixture2*",
|
||||
"*test_1*",
|
||||
"*TEARDOWN M fixture1*",
|
||||
],
|
||||
consecutive=True,
|
||||
)
|
||||
|
||||
|
||||
def test_early_teardown_complex(
|
||||
pytester: Pytester,
|
||||
) -> None:
|
||||
pytester.makepyfile(
|
||||
|
@ -4634,6 +4676,141 @@ def test_teardown_high_scope_fixture_at_last_dependent_item_complex(
|
|||
)
|
||||
|
||||
|
||||
def test_fixtureclosure_contains_shadowed_fixtures(pytester: Pytester):
|
||||
pytester.makeconftest(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
@pytest.fixture
|
||||
def fixt0():
|
||||
pass
|
||||
|
||||
@pytest.fixture
|
||||
def fixt1():
|
||||
pass
|
||||
|
||||
@pytest.fixture
|
||||
def fixt2(fixt0, fixt1):
|
||||
pass
|
||||
"""
|
||||
)
|
||||
pytester.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
@pytest.fixture
|
||||
def fixt2(fixt2):
|
||||
pass
|
||||
|
||||
@pytest.fixture
|
||||
def fixt3():
|
||||
pass
|
||||
|
||||
def test(fixt2, fixt3):
|
||||
pass
|
||||
"""
|
||||
)
|
||||
result = pytester.runpytest("--setup-show")
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*SETUP F fixt0*",
|
||||
"*SETUP F fixt1*",
|
||||
"*SETUP F fixt2 (fixtures used: fixt0, fixt1)*",
|
||||
"*SETUP F fixt2 (fixtures used: fixt2)*",
|
||||
"*SETUP F fixt3*",
|
||||
"*test (fixtures used: fixt0, fixt1, fixt2, fixt3)*",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_early_teardown_fixture_both_overriden_and_being_used(pytester: Pytester):
|
||||
pytester.makeconftest(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def shadowed():
|
||||
pass
|
||||
|
||||
@pytest.fixture
|
||||
def intermediary(shadowed):
|
||||
pass
|
||||
"""
|
||||
)
|
||||
pytester.makepyfile(
|
||||
test_a="""
|
||||
import pytest
|
||||
|
||||
def test_0(shadowed):
|
||||
pass
|
||||
""",
|
||||
test_b="""
|
||||
import pytest
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def shadowed():
|
||||
pass
|
||||
|
||||
def test_1():
|
||||
pass
|
||||
|
||||
def test_2(shadowed, intermediary):
|
||||
pass
|
||||
""",
|
||||
)
|
||||
result = pytester.runpytest("--setup-show")
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*SETUP S shadowed*",
|
||||
"*test_0*",
|
||||
"*TEARDOWN S shadowed*",
|
||||
"*test_1*",
|
||||
"*SETUP S shadowed*",
|
||||
"*SETUP F intermediary*",
|
||||
"*test_2*",
|
||||
"*TEARDOWN F intermediary*",
|
||||
"*TEARDOWN S shadowed*",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_early_teardown_homonym_session_scoped_fixtures(pytester: Pytester):
|
||||
"""Session-scoped fixtures, as only argname and baseid are active in their
|
||||
corresponding arg keys."""
|
||||
pytester.makepyfile(
|
||||
test_a="""
|
||||
import pytest
|
||||
@pytest.fixture(scope='session')
|
||||
def fixture():
|
||||
yield 0
|
||||
|
||||
def test_0(fixture):
|
||||
assert fixture == 0
|
||||
""",
|
||||
test_b="""
|
||||
import pytest
|
||||
@pytest.fixture(scope='session')
|
||||
def fixture():
|
||||
yield 1
|
||||
|
||||
def test_1(fixture):
|
||||
assert fixture == 1
|
||||
""",
|
||||
)
|
||||
result = pytester.runpytest("--setup-show")
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*SETUP S fixture*",
|
||||
"*test_0*",
|
||||
"*TEARDOWN S fixture*",
|
||||
"*SETUP S fixture",
|
||||
"*test_1*",
|
||||
"*TEARDOWN S fixture*",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_reorder_with_nonparametrized_fixtures(pytester: Pytester):
|
||||
path = pytester.makepyfile(
|
||||
"""
|
||||
|
@ -4696,6 +4873,326 @@ def test_reorder_with_both_parametrized_and_nonparametrized_fixtures(
|
|||
result.stdout.fnmatch_lines([f"*test_{i}*" for i in [0, 2, 1]])
|
||||
|
||||
|
||||
def test_early_teardown_parametrized_homonym_parent_fixture(pytester: Pytester):
|
||||
pytester.makeconftest(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
@pytest.fixture(params=[0, 1], scope='session')
|
||||
def fixture(request):
|
||||
pass
|
||||
"""
|
||||
)
|
||||
pytester.makepyfile(
|
||||
test_a="""
|
||||
import pytest
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def fixture(fixture):
|
||||
pass
|
||||
|
||||
def test_0(fixture):
|
||||
pass
|
||||
|
||||
def test_1():
|
||||
pass
|
||||
|
||||
def test_2(fixture):
|
||||
pass
|
||||
""",
|
||||
test_b="""
|
||||
def test_3(fixture):
|
||||
pass
|
||||
|
||||
def test_4():
|
||||
pass
|
||||
""",
|
||||
test_c="""
|
||||
import pytest
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def fixture():
|
||||
pass
|
||||
|
||||
def test_5(fixture):
|
||||
pass
|
||||
""",
|
||||
)
|
||||
result = pytester.runpytest("--setup-show")
|
||||
result.stdout.re_match_lines(
|
||||
[
|
||||
r"\s*SETUP S fixture\[0\].*",
|
||||
r"\s*SETUP S fixture.*",
|
||||
r".*test_0\[0\].*",
|
||||
r".*test_2\[0\].*",
|
||||
r"\s*TEARDOWN S fixture.*",
|
||||
r".*test_3\[0\].*",
|
||||
r"\s*TEARDOWN S fixture\[0\].*",
|
||||
r"\s*SETUP S fixture\[1\].*",
|
||||
r"\s*SETUP S fixture.*",
|
||||
r".*test_0\[1\].*",
|
||||
r".*test_2\[1\].*",
|
||||
r"\s*TEARDOWN S fixture.*",
|
||||
r".*test_3\[1\].*",
|
||||
r"\s*TEARDOWN S fixture\[1\].*",
|
||||
r".*test_1.*",
|
||||
r".*test_4.*",
|
||||
r"\s*SETUP S fixture.*",
|
||||
r".*test_5.*",
|
||||
r"\s*TEARDOWN S fixture.*",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_early_teardown_parametrized_homonym_parent_and_child_fixture(
|
||||
pytester: Pytester,
|
||||
):
|
||||
pytester.makeconftest(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
@pytest.fixture(params=[0, 1], scope='session')
|
||||
def fixture(request):
|
||||
pass
|
||||
"""
|
||||
)
|
||||
pytester.makepyfile(
|
||||
test_a="""
|
||||
import pytest
|
||||
|
||||
@pytest.fixture(params=[2, 3], scope='session')
|
||||
def fixture(request, fixture):
|
||||
pass
|
||||
|
||||
def test_0(fixture):
|
||||
pass
|
||||
|
||||
def test_1():
|
||||
pass
|
||||
|
||||
def test_2(fixture):
|
||||
pass
|
||||
""",
|
||||
test_b="""
|
||||
def test_3(fixture):
|
||||
pass
|
||||
|
||||
def test_4():
|
||||
pass
|
||||
|
||||
def test_5(fixture):
|
||||
pass
|
||||
""",
|
||||
)
|
||||
result = pytester.runpytest("--setup-show")
|
||||
result.stdout.re_match_lines(
|
||||
[
|
||||
r"\s*SETUP S fixture.*",
|
||||
r"\s*SETUP S fixture \(fixtures used: fixture\)\[2\].*",
|
||||
r".*test_0.*",
|
||||
r".*test_2.*",
|
||||
r"\s*TEARDOWN S fixture\[2\].*",
|
||||
r"\s*TEARDOWN S fixture.*",
|
||||
r"\s*SETUP S fixture.*",
|
||||
r"\s*SETUP S fixture \(fixtures used: fixture\)\[3\].*",
|
||||
r".*test_0.*",
|
||||
r".*test_2.*",
|
||||
r"\s*TEARDOWN S fixture\[3\].*",
|
||||
r"\s*TEARDOWN S fixture.*",
|
||||
r".*test_1.*",
|
||||
r"\s*SETUP S fixture\[0\].*",
|
||||
r".*test_3.*",
|
||||
r".*test_5.*",
|
||||
r"\s*TEARDOWN S fixture\[0\].*",
|
||||
r"\s*SETUP S fixture\[1\].*",
|
||||
r".*test_3.*",
|
||||
r".*test_5.*",
|
||||
r"\s*TEARDOWN S fixture\[1\].*",
|
||||
r".*test_4.*",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_early_teardown_parametrized_overriden_and_overrider_fixture(
|
||||
pytester: Pytester,
|
||||
):
|
||||
pytester.makeconftest(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
@pytest.fixture(params=[0, 1], scope='session')
|
||||
def fixture(request):
|
||||
pass
|
||||
"""
|
||||
)
|
||||
pytester.makepyfile(
|
||||
test_a="""
|
||||
def test_0(fixture):
|
||||
pass
|
||||
|
||||
def test_1():
|
||||
pass
|
||||
|
||||
def test_2(fixture):
|
||||
pass
|
||||
|
||||
def test_3():
|
||||
pass
|
||||
""",
|
||||
test_b="""
|
||||
import pytest
|
||||
|
||||
@pytest.fixture(params=[2, 3], scope='session')
|
||||
def fixture(request):
|
||||
pass
|
||||
|
||||
def test_4(fixture):
|
||||
pass
|
||||
|
||||
def test_5():
|
||||
pass
|
||||
|
||||
def test_6(fixture):
|
||||
pass
|
||||
""",
|
||||
)
|
||||
result = pytester.runpytest("--setup-show")
|
||||
result.stdout.re_match_lines(
|
||||
[
|
||||
r"\s*SETUP S fixture\[0\].*",
|
||||
r".*test_0.*",
|
||||
r".*test_2.*",
|
||||
r"\s*TEARDOWN S fixture\[0\].*",
|
||||
r"\s*SETUP S fixture\[1\].*",
|
||||
r".*test_0.*",
|
||||
r".*test_2.*",
|
||||
r"\s*TEARDOWN S fixture\[1\].*",
|
||||
r".*test_3.*",
|
||||
r"\s*SETUP S fixture\[2\].*",
|
||||
r".*test_4.*",
|
||||
r".*test_6.*",
|
||||
r"\s*TEARDOWN S fixture\[2\].*",
|
||||
r"\s*SETUP S fixture\[3\].*",
|
||||
r".*test_4.*",
|
||||
r".*test_6.*",
|
||||
r"\s*TEARDOWN S fixture\[3\].*",
|
||||
r".*test_5.*",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_early_teardown_indirectly_parametrized_fixture(pytester: Pytester):
|
||||
pytester.makeconftest(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
@pytest.fixture(params=[0, 1], scope='session')
|
||||
def fixture(request):
|
||||
pass
|
||||
"""
|
||||
)
|
||||
pytester.makepyfile(
|
||||
test_a="""
|
||||
import pytest
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def fixture(request, fixture):
|
||||
pass
|
||||
|
||||
@pytest.mark.parametrize('fixture', [2, 3], indirect=True)
|
||||
def test_0(fixture):
|
||||
pass
|
||||
|
||||
def test_1():
|
||||
pass
|
||||
|
||||
def test_2(fixture):
|
||||
pass
|
||||
|
||||
def test_3():
|
||||
pass
|
||||
|
||||
def test_4(fixture):
|
||||
pass
|
||||
""",
|
||||
test_b="""
|
||||
def test_5():
|
||||
pass
|
||||
|
||||
def test_6(fixture):
|
||||
pass
|
||||
""",
|
||||
)
|
||||
result = pytester.runpytest("--setup-show")
|
||||
result.stdout.re_match_lines(
|
||||
[
|
||||
r"\s*SETUP S fixture.*",
|
||||
r"\s*SETUP S fixture \(fixtures used: fixture\)\[2\].*",
|
||||
r".*test_0\[2\].*",
|
||||
r"\s*TEARDOWN S fixture\[2\].*",
|
||||
# One might expect conftest::fixture not to tear down here, as test_0[3] will use it afterwards,
|
||||
# but since conftest::fixture gets parameter although it's not parametrized on test_0, it tears down
|
||||
# itself as soon as it sees the parameter, which has nothing to do with, has changed from 2 to 3.
|
||||
# In the future we could change callspec param dict to have the fixture baseid in its key as well to
|
||||
# satisfy this expectation.
|
||||
r"\s*TEARDOWN S fixture.*",
|
||||
r"\s*SETUP S fixture.*",
|
||||
r"\s*SETUP S fixture \(fixtures used: fixture\)\[3\].*",
|
||||
r".*test_0\[3\].*",
|
||||
r"\s*TEARDOWN S fixture\[3\].*",
|
||||
r"\s*TEARDOWN S fixture.*",
|
||||
r".*test_1.*",
|
||||
r"\s*SETUP S fixture\[0\].*",
|
||||
r"\s*SETUP S fixture.*",
|
||||
r".*test_2\[0\].*",
|
||||
r".*test_4\[0\].*",
|
||||
r"\s*TEARDOWN S fixture.*",
|
||||
r".*test_6\[0\].*",
|
||||
r"\s*TEARDOWN S fixture\[0\].*",
|
||||
r"\s*SETUP S fixture\[1\].*",
|
||||
r"\s*SETUP S fixture.*",
|
||||
r".*test_2\[1\].*",
|
||||
r".*test_4\[1\].*",
|
||||
r"\s*TEARDOWN S fixture.*",
|
||||
r".*test_6\[1\].*",
|
||||
r"\s*TEARDOWN S fixture\[1\].*",
|
||||
r".*test_3.*",
|
||||
r".*test_5.*",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_item_determines_which_def_to_use_not_the_requester(pytester: Pytester):
|
||||
pytester.makeconftest(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
@pytest.fixture
|
||||
def fixture():
|
||||
yield 1
|
||||
|
||||
@pytest.fixture
|
||||
def intermediary(fixture):
|
||||
yield fixture
|
||||
"""
|
||||
)
|
||||
pytester.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
@pytest.fixture
|
||||
def fixture():
|
||||
yield 2
|
||||
|
||||
def test_0(intermediary):
|
||||
"If requester(intermediary here) was to determine which fixture to use, we should have had 1"
|
||||
assert intermediary == 2
|
||||
"""
|
||||
)
|
||||
result = pytester.runpytest()
|
||||
result.ret == 0
|
||||
|
||||
|
||||
def test_add_new_test_dependent_on_a_fixuture_and_use_nfplugin(pytester: Pytester):
|
||||
test_module_string = """
|
||||
import pytest
|
||||
|
@ -4765,9 +5262,6 @@ def test_last_dependent_test_on_a_fixture_is_in_last_failed_using_lfplugin(
|
|||
)
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="We do not attempt to tear down early the fixture that is overridden and also is used"
|
||||
)
|
||||
def test_early_teardown_of_overridden_and_being_used_fixture(
|
||||
pytester: Pytester,
|
||||
) -> None:
|
||||
|
@ -4777,8 +5271,8 @@ def test_early_teardown_of_overridden_and_being_used_fixture(
|
|||
|
||||
@pytest.fixture(scope='module')
|
||||
def fixture0():
|
||||
yield None
|
||||
print("Tearing down higher-level fixture0")
|
||||
# This fixture is used by its overrider
|
||||
pass
|
||||
"""
|
||||
)
|
||||
pytester.makepyfile(
|
||||
|
@ -4787,23 +5281,26 @@ def test_early_teardown_of_overridden_and_being_used_fixture(
|
|||
|
||||
@pytest.fixture(scope='module')
|
||||
def fixture0(fixture0):
|
||||
yield None
|
||||
print("Tearing down lower-level fixture0")
|
||||
pass
|
||||
|
||||
def test_0(fixture0):
|
||||
pass
|
||||
|
||||
def test_1():
|
||||
print("Both `fixture0`s should have been torn down")
|
||||
pass
|
||||
"""
|
||||
)
|
||||
result = pytester.runpytest("-s")
|
||||
result = pytester.runpytest("--setup-show")
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*Tearing down lower-level fixture0*",
|
||||
"*Tearing down higher-level fixture0*",
|
||||
"*Both `fixture0`s should have been torn down*",
|
||||
]
|
||||
"*SETUP M fixture0*",
|
||||
"*SETUP M fixture0*",
|
||||
"*test_0*",
|
||||
"*TEARDOWN M fixture0*",
|
||||
"*TEARDOWN M fixture0*",
|
||||
"*test_1*",
|
||||
],
|
||||
consecutive=True,
|
||||
)
|
||||
|
||||
|
||||
|
@ -5010,3 +5507,125 @@ def test_early_teardown_does_not_occur_for_pseudo_fixtures(pytester: Pytester) -
|
|||
import functools
|
||||
|
||||
assert not any([isinstance(item.teardown, functools.partial) for item in items])
|
||||
|
||||
|
||||
def test_fixture_info_after_dynamic_parametrize(pytester: Pytester) -> None:
|
||||
pytester.makeconftest(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
@pytest.fixture(scope='session', params=[0, 1])
|
||||
def fixture1(request):
|
||||
pass
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def fixture2(fixture1):
|
||||
pass
|
||||
|
||||
@pytest.fixture(scope='session', params=[2, 3])
|
||||
def fixture3(request, fixture2):
|
||||
pass
|
||||
"""
|
||||
)
|
||||
pytester.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.parametrize("fixture2", [4, 5], scope='session')
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def fixture4():
|
||||
pass
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def fixture2(fixture3, fixture4):
|
||||
pass
|
||||
|
||||
def test(fixture2):
|
||||
pass
|
||||
"""
|
||||
)
|
||||
res = pytester.inline_run()
|
||||
res.assertoutcome(passed=2)
|
||||
|
||||
|
||||
def test_reordering_after_dynamic_parametrize(pytester: Pytester):
|
||||
pytester.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
if metafunc.definition.name == "test_0":
|
||||
metafunc.parametrize("fixture2", [0])
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def fixture1():
|
||||
pass
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def fixture2(fixture1):
|
||||
pass
|
||||
|
||||
def test_0(fixture2):
|
||||
pass
|
||||
|
||||
def test_1():
|
||||
pass
|
||||
|
||||
def test_2(fixture1):
|
||||
pass
|
||||
"""
|
||||
)
|
||||
result = pytester.runpytest("--collect-only")
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*test_0*",
|
||||
"*test_1*",
|
||||
"*test_2*",
|
||||
],
|
||||
consecutive=True,
|
||||
)
|
||||
|
||||
|
||||
def test_dont_recompute_dependency_tree_if_no_dynamic_parametrize(
|
||||
pytester: Pytester, monkeypatch: MonkeyPatch
|
||||
):
|
||||
pytester.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
if metafunc.definition.name == "test_0":
|
||||
metafunc.parametrize("fixture", [0])
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def fixture():
|
||||
pass
|
||||
|
||||
def test_0(fixture):
|
||||
pass
|
||||
|
||||
def test_1():
|
||||
pass
|
||||
|
||||
@pytest.mark.parametrize("fixture", [0])
|
||||
def test_2(fixture):
|
||||
pass
|
||||
|
||||
@pytest.mark.parametrize("fixture", [0], indirect=True)
|
||||
def test_3(fixture):
|
||||
pass
|
||||
"""
|
||||
)
|
||||
from _pytest.fixtures import FixtureManager
|
||||
|
||||
with monkeypatch.context() as m:
|
||||
mocked_function = Mock(wraps=FixtureManager.getfixtureclosure)
|
||||
m.setattr(FixtureManager, "getfixtureclosure", mocked_function)
|
||||
pytester.inline_run()
|
||||
assert len(mocked_function.call_args_list) == 5
|
||||
assert mocked_function.call_args_list[0].args[0].nodeid.endswith("test_0")
|
||||
assert mocked_function.call_args_list[1].args[0].nodeid.endswith("test_0")
|
||||
assert mocked_function.call_args_list[2].args[0].nodeid.endswith("test_1")
|
||||
assert mocked_function.call_args_list[3].args[0].nodeid.endswith("test_2")
|
||||
assert mocked_function.call_args_list[4].args[0].nodeid.endswith("test_3")
|
||||
|
|
|
@ -35,6 +35,7 @@ class TestMetafunc:
|
|||
# initialization.
|
||||
class FuncFixtureInfoMock:
|
||||
name2fixturedefs = {}
|
||||
name2num_fixturedefs_used = {}
|
||||
|
||||
def __init__(self, names):
|
||||
self.names_closure = names
|
||||
|
@ -55,6 +56,7 @@ class TestMetafunc:
|
|||
names = getfuncargnames(func)
|
||||
fixtureinfo: Any = FuncFixtureInfoMock(names)
|
||||
definition: Any = DefinitionMock._create(obj=func, _nodeid="mock::nodeid")
|
||||
definition._fixtureinfo = fixtureinfo
|
||||
definition.session = SessionMock(FixtureManagerMock({}))
|
||||
|
||||
return python.Metafunc(definition, fixtureinfo, config, _ispytest=True)
|
||||
|
|
Loading…
Reference in New Issue