Apply comments and and an improvement
This commit is contained in:
parent
aa5d09deef
commit
bbf594903e
|
@ -1326,6 +1326,12 @@ def _get_direct_parametrize_args(node: nodes.Node) -> List[str]:
|
||||||
return parametrize_argnames
|
return parametrize_argnames
|
||||||
|
|
||||||
|
|
||||||
|
def deduplicate_names(seq: Iterable[str]) -> Tuple[str, ...]:
|
||||||
|
"""De-duplicate the sequence of names while keeping the original order."""
|
||||||
|
# Ideally we would use a set, but it does not preserve insertion order.
|
||||||
|
return tuple(dict.fromkeys(seq))
|
||||||
|
|
||||||
|
|
||||||
class FixtureManager:
|
class FixtureManager:
|
||||||
"""pytest fixture definitions and information is stored and managed
|
"""pytest fixture definitions and information is stored and managed
|
||||||
from this class.
|
from this class.
|
||||||
|
@ -1404,14 +1410,9 @@ class FixtureManager:
|
||||||
usefixtures = tuple(
|
usefixtures = tuple(
|
||||||
arg for mark in node.iter_markers(name="usefixtures") for arg in mark.args
|
arg for mark in node.iter_markers(name="usefixtures") for arg in mark.args
|
||||||
)
|
)
|
||||||
initialnames = cast(
|
initialnames = deduplicate_names(
|
||||||
Tuple[str],
|
|
||||||
tuple(
|
|
||||||
dict.fromkeys(
|
|
||||||
tuple(self._getautousenames(node.nodeid)) + usefixtures + argnames
|
tuple(self._getautousenames(node.nodeid)) + usefixtures + argnames
|
||||||
)
|
)
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]] = {}
|
arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]] = {}
|
||||||
names_closure = self.getfixtureclosure(
|
names_closure = self.getfixtureclosure(
|
||||||
|
@ -1459,23 +1460,19 @@ class FixtureManager:
|
||||||
def getfixtureclosure(
|
def getfixtureclosure(
|
||||||
self,
|
self,
|
||||||
parentnode: nodes.Node,
|
parentnode: nodes.Node,
|
||||||
initialnames: Tuple[str],
|
initialnames: Tuple[str, ...],
|
||||||
arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]],
|
arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]],
|
||||||
ignore_args: Sequence[str] = (),
|
ignore_args: Sequence[str] = (),
|
||||||
) -> List[str]:
|
) -> List[str]:
|
||||||
# Collect the closure of all fixtures, starting with the given
|
# Collect the closure of all fixtures, starting with the given
|
||||||
# initialnames as the initial set. As we have to visit all
|
# initialnames containing function arguments, `usefixture` markers
|
||||||
# factory definitions anyway, we also populate arg2fixturedefs
|
# and `autouse` fixtures as the initial set. As we have to visit all
|
||||||
# mapping so that the caller can reuse it and does not have
|
# factory definitions anyway, we also populate arg2fixturedefs mapping
|
||||||
# to re-discover fixturedefs again for each fixturename
|
# for the args missing therein so that the caller can reuse it and does
|
||||||
|
# not have to re-discover fixturedefs again for each fixturename
|
||||||
# (discovering matching fixtures for a given name/node is expensive).
|
# (discovering matching fixtures for a given name/node is expensive).
|
||||||
|
|
||||||
fixturenames_closure = list(initialnames)
|
fixturenames_closure = initialnames
|
||||||
|
|
||||||
def merge(otherlist: Iterable[str]) -> None:
|
|
||||||
for arg in otherlist:
|
|
||||||
if arg not in fixturenames_closure:
|
|
||||||
fixturenames_closure.append(arg)
|
|
||||||
|
|
||||||
lastlen = -1
|
lastlen = -1
|
||||||
parentid = parentnode.nodeid
|
parentid = parentnode.nodeid
|
||||||
|
@ -1489,7 +1486,9 @@ class FixtureManager:
|
||||||
if fixturedefs:
|
if fixturedefs:
|
||||||
arg2fixturedefs[argname] = fixturedefs
|
arg2fixturedefs[argname] = fixturedefs
|
||||||
if argname in arg2fixturedefs:
|
if argname in arg2fixturedefs:
|
||||||
merge(arg2fixturedefs[argname][-1].argnames)
|
fixturenames_closure = deduplicate_names(
|
||||||
|
fixturenames_closure + arg2fixturedefs[argname][-1].argnames
|
||||||
|
)
|
||||||
|
|
||||||
def sort_by_scope(arg_name: str) -> Scope:
|
def sort_by_scope(arg_name: str) -> Scope:
|
||||||
try:
|
try:
|
||||||
|
@ -1499,8 +1498,7 @@ class FixtureManager:
|
||||||
else:
|
else:
|
||||||
return fixturedefs[-1]._scope
|
return fixturedefs[-1]._scope
|
||||||
|
|
||||||
fixturenames_closure.sort(key=sort_by_scope, reverse=True)
|
return sorted(fixturenames_closure, key=sort_by_scope, reverse=True)
|
||||||
return fixturenames_closure
|
|
||||||
|
|
||||||
def pytest_generate_tests(self, metafunc: "Metafunc") -> None:
|
def pytest_generate_tests(self, metafunc: "Metafunc") -> None:
|
||||||
"""Generate new tests based on parametrized fixtures used by the given metafunc"""
|
"""Generate new tests based on parametrized fixtures used by the given metafunc"""
|
||||||
|
|
|
@ -11,7 +11,6 @@ import warnings
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from functools import wraps
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
@ -382,12 +381,13 @@ del _EmptyClass
|
||||||
# fmt: on
|
# fmt: on
|
||||||
|
|
||||||
|
|
||||||
def unwrap_metafunc_parametrize_and_possibly_prune_dependency_tree(metafunc):
|
def prune_dependency_tree_if_test_is_dynamically_parametrized(metafunc):
|
||||||
metafunc.parametrize = metafunc._parametrize
|
if metafunc._calls:
|
||||||
del metafunc._parametrize
|
|
||||||
if metafunc.has_dynamic_parametrize:
|
|
||||||
# Dynamic direct parametrization may have shadowed some fixtures
|
# Dynamic direct parametrization may have shadowed some fixtures
|
||||||
# so make sure we update what the function really needs.
|
# so make sure we update what the function really needs. Note that
|
||||||
|
# we didn't need to do this if only indirect dynamic parametrization
|
||||||
|
# had taken place, but anyway we did it as differentiating between direct
|
||||||
|
# and indirect requires a dirty hack.
|
||||||
definition = metafunc.definition
|
definition = metafunc.definition
|
||||||
fixture_closure = definition.parent.session._fixturemanager.getfixtureclosure(
|
fixture_closure = definition.parent.session._fixturemanager.getfixtureclosure(
|
||||||
definition,
|
definition,
|
||||||
|
@ -396,7 +396,6 @@ def unwrap_metafunc_parametrize_and_possibly_prune_dependency_tree(metafunc):
|
||||||
ignore_args=_get_direct_parametrize_args(definition) + ["request"],
|
ignore_args=_get_direct_parametrize_args(definition) + ["request"],
|
||||||
)
|
)
|
||||||
definition._fixtureinfo.names_closure[:] = fixture_closure
|
definition._fixtureinfo.names_closure[:] = fixture_closure
|
||||||
del metafunc.has_dynamic_parametrize
|
|
||||||
|
|
||||||
|
|
||||||
class PyCollector(PyobjMixin, nodes.Collector):
|
class PyCollector(PyobjMixin, nodes.Collector):
|
||||||
|
@ -503,22 +502,12 @@ class PyCollector(PyobjMixin, nodes.Collector):
|
||||||
module=module,
|
module=module,
|
||||||
_ispytest=True,
|
_ispytest=True,
|
||||||
)
|
)
|
||||||
methods = [unwrap_metafunc_parametrize_and_possibly_prune_dependency_tree]
|
methods = [prune_dependency_tree_if_test_is_dynamically_parametrized]
|
||||||
if hasattr(module, "pytest_generate_tests"):
|
if hasattr(module, "pytest_generate_tests"):
|
||||||
methods.append(module.pytest_generate_tests)
|
methods.append(module.pytest_generate_tests)
|
||||||
if cls is not None and hasattr(cls, "pytest_generate_tests"):
|
if cls is not None and hasattr(cls, "pytest_generate_tests"):
|
||||||
methods.append(cls().pytest_generate_tests)
|
methods.append(cls().pytest_generate_tests)
|
||||||
|
|
||||||
setattr(metafunc, "has_dynamic_parametrize", False)
|
|
||||||
|
|
||||||
@wraps(metafunc.parametrize)
|
|
||||||
def set_has_dynamic_parametrize(*args, **kwargs):
|
|
||||||
setattr(metafunc, "has_dynamic_parametrize", True)
|
|
||||||
metafunc._parametrize(*args, **kwargs) # type: ignore[attr-defined]
|
|
||||||
|
|
||||||
setattr(metafunc, "_parametrize", metafunc.parametrize)
|
|
||||||
setattr(metafunc, "parametrize", set_has_dynamic_parametrize)
|
|
||||||
|
|
||||||
# pytest_generate_tests impls call metafunc.parametrize() which fills
|
# pytest_generate_tests impls call metafunc.parametrize() which fills
|
||||||
# metafunc._calls, the outcome of the hook.
|
# metafunc._calls, the outcome of the hook.
|
||||||
self.ihook.pytest_generate_tests.call_extra(methods, dict(metafunc=metafunc))
|
self.ihook.pytest_generate_tests.call_extra(methods, dict(metafunc=metafunc))
|
||||||
|
|
|
@ -4681,3 +4681,10 @@ def test_dont_recompute_dependency_tree_if_no_dynamic_parametrize(pytester: Pyte
|
||||||
)
|
)
|
||||||
reprec = pytester.inline_run()
|
reprec = pytester.inline_run()
|
||||||
reprec.assertoutcome(passed=5)
|
reprec.assertoutcome(passed=5)
|
||||||
|
|
||||||
|
|
||||||
|
def test_deduplicate_names(pytester: Pytester) -> None:
|
||||||
|
items = fixtures.deduplicate_names("abacd")
|
||||||
|
assert items == ("a", "b", "c", "d")
|
||||||
|
items = fixtures.deduplicate_names(items + ("g", "f", "g", "e", "b"))
|
||||||
|
assert items == ("a", "b", "c", "d", "g", "f", "e")
|
||||||
|
|
Loading…
Reference in New Issue