Fix a bug in tests;Add docstring to them;Apply review comments

This commit is contained in:
Sadra Barikbin 2023-09-17 01:17:51 +03:30
parent 7c56b16faf
commit 20ea215492
3 changed files with 40 additions and 28 deletions

View File

@ -1074,7 +1074,7 @@ class FixtureDef(Generic[FixtureValue]):
) )
class IdentityFixture(FixtureDef[FixtureValue]): class IdentityFixtureDef(FixtureDef[FixtureValue]):
def __init__( def __init__(
self, self,
fixturemanager: "FixtureManager", fixturemanager: "FixtureManager",
@ -1476,11 +1476,11 @@ class FixtureManager:
initialnames = deduplicate_names(autousenames, usefixturesnames, argnames) initialnames = deduplicate_names(autousenames, usefixturesnames, argnames)
direct_parametrize_args = _get_direct_parametrize_args(node) direct_parametrize_args = _get_direct_parametrize_args(node)
arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]] = {}
names_closure, arg2fixturedefs = self.getfixtureclosure( names_closure = self.getfixtureclosure(
parentnode=node, parentnode=node,
initialnames=initialnames, initialnames=initialnames,
arg2fixturedefs=None, arg2fixturedefs=arg2fixturedefs,
ignore_args=direct_parametrize_args, ignore_args=direct_parametrize_args,
) )
@ -1524,9 +1524,9 @@ class FixtureManager:
self, self,
parentnode: nodes.Node, parentnode: nodes.Node,
initialnames: Tuple[str, ...], initialnames: Tuple[str, ...],
arg2fixturedefs: Union[Dict[str, Sequence[FixtureDef[Any]]], None], arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]],
ignore_args: AbstractSet[str], ignore_args: AbstractSet[str],
) -> Tuple[List[str], Dict[str, Sequence[FixtureDef[Any]]]]: ) -> List[str]:
# Collect the closure of all fixtures, starting with the given # Collect the closure of all fixtures, starting with the given
# initialnames containing function arguments, `usefixture` markers # initialnames containing function arguments, `usefixture` markers
# and `autouse` fixtures as the initial set. As we have to visit all # and `autouse` fixtures as the initial set. As we have to visit all
@ -1535,8 +1535,6 @@ class FixtureManager:
# not have to re-discover fixturedefs again for each fixturename # 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).
if arg2fixturedefs is None:
arg2fixturedefs = {}
parentid = parentnode.nodeid parentid = parentnode.nodeid
fixturenames_closure = list(initialnames) fixturenames_closure = list(initialnames)
@ -1552,7 +1550,7 @@ class FixtureManager:
arg2fixturedefs[argname] = fixturedefs arg2fixturedefs[argname] = fixturedefs
else: else:
fixturedefs = arg2fixturedefs[argname] fixturedefs = arg2fixturedefs[argname]
if fixturedefs and not isinstance(fixturedefs[-1], IdentityFixture): if fixturedefs and not isinstance(fixturedefs[-1], IdentityFixtureDef):
for arg in fixturedefs[-1].argnames: for arg in fixturedefs[-1].argnames:
if arg not in fixturenames_closure: if arg not in fixturenames_closure:
fixturenames_closure.append(arg) fixturenames_closure.append(arg)
@ -1566,7 +1564,7 @@ class FixtureManager:
return fixturedefs[-1]._scope return fixturedefs[-1]._scope
fixturenames_closure.sort(key=sort_by_scope, reverse=True) fixturenames_closure.sort(key=sort_by_scope, reverse=True)
return fixturenames_closure, arg2fixturedefs 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"""

View File

@ -14,7 +14,6 @@ from functools import partial
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
from typing import Callable from typing import Callable
from typing import cast
from typing import Dict from typing import Dict
from typing import final from typing import final
from typing import Generator from typing import Generator
@ -63,7 +62,7 @@ from _pytest.fixtures import _get_direct_parametrize_args
from _pytest.fixtures import FixtureDef from _pytest.fixtures import FixtureDef
from _pytest.fixtures import FuncFixtureInfo from _pytest.fixtures import FuncFixtureInfo
from _pytest.fixtures import get_scope_node from _pytest.fixtures import get_scope_node
from _pytest.fixtures import IdentityFixture from _pytest.fixtures import IdentityFixtureDef
from _pytest.main import Session from _pytest.main import Session
from _pytest.mark import MARK_GEN from _pytest.mark import MARK_GEN
from _pytest.mark import ParameterSet from _pytest.mark import ParameterSet
@ -492,10 +491,14 @@ class PyCollector(PyobjMixin, nodes.Collector):
def prune_dependency_tree_if_test_is_directly_parametrized(metafunc: Metafunc): def prune_dependency_tree_if_test_is_directly_parametrized(metafunc: Metafunc):
# Direct (those with `indirect=False`) parametrizations taking place in # Direct (those with `indirect=False`) parametrizations taking place in
# module/class-specific `pytest_generate_tests` hooks, a.k.a dynamic direct # module/class-specific `pytest_generate_tests` hooks, a.k.a dynamic direct
# parametrizations, may have shadowed some fixtures so make sure we update what # parametrizations using `metafunc.parametrize`, may have shadowed some
# the function really needs. # fixtures, making some fixtures no longer reachable. Update the dependency
if metafunc.has_direct_parametrization: # tree to reflect what the item really needs.
metafunc.update_dependency_tree() # Note that direct parametrizations using `@pytest.mark.parametrize` have
# already been considered into making the closure using the `ignore_args`
# arg to `getfixtureclosure`.
if metafunc._has_direct_parametrization:
metafunc._update_dependency_tree()
methods = [prune_dependency_tree_if_test_is_directly_parametrized] methods = [prune_dependency_tree_if_test_is_directly_parametrized]
if hasattr(module, "pytest_generate_tests"): if hasattr(module, "pytest_generate_tests"):
@ -1223,7 +1226,7 @@ class Metafunc:
self._calls: List[CallSpec2] = [] self._calls: List[CallSpec2] = []
# Whether it's ever been directly parametrized, i.e. with `indirect=False`. # Whether it's ever been directly parametrized, i.e. with `indirect=False`.
self.has_direct_parametrization = False self._has_direct_parametrization = False
def parametrize( def parametrize(
self, self,
@ -1373,11 +1376,11 @@ class Metafunc:
for argname in argnames: for argname in argnames:
if arg_directness[argname] == "indirect": if arg_directness[argname] == "indirect":
continue continue
self.has_direct_parametrization = True self._has_direct_parametrization = True
if name2pseudofixturedef is not None and argname in name2pseudofixturedef: if name2pseudofixturedef is not None and argname in name2pseudofixturedef:
fixturedef = name2pseudofixturedef[argname] fixturedef = name2pseudofixturedef[argname]
else: else:
fixturedef = IdentityFixture( fixturedef = IdentityFixtureDef(
self.definition.session._fixturemanager, self.definition.session._fixturemanager,
argname, argname,
scope_, scope_,
@ -1546,10 +1549,11 @@ class Metafunc:
pytrace=False, pytrace=False,
) )
def update_dependency_tree(self) -> None: def _update_dependency_tree(self) -> None:
definition = self.definition definition = self.definition
fm = cast(nodes.Node, definition.parent).session._fixturemanager assert definition.parent is not None
fixture_closure, _ = fm.getfixtureclosure( fm = definition.parent.session._fixturemanager
fixture_closure = fm.getfixtureclosure(
parentnode=definition, parentnode=definition,
initialnames=definition._fixtureinfo.initialnames, initialnames=definition._fixtureinfo.initialnames,
arg2fixturedefs=definition._fixtureinfo.name2fixturedefs, arg2fixturedefs=definition._fixtureinfo.name2fixturedefs,

View File

@ -4535,6 +4535,11 @@ def test_yield_fixture_with_no_value(pytester: Pytester) -> None:
def test_fixture_info_after_dynamic_parametrize(pytester: Pytester) -> None: def test_fixture_info_after_dynamic_parametrize(pytester: Pytester) -> None:
"""
Item dependency tree should get prunned before `FixtureManager::pytest_generate_tests`
hook implementation because it attempts to parametrize on the fixtures in the
fixture closure.
"""
pytester.makeconftest( pytester.makeconftest(
""" """
import pytest import pytest
@ -4559,11 +4564,7 @@ def test_fixture_info_after_dynamic_parametrize(pytester: Pytester) -> None:
metafunc.parametrize("fixture2", [4, 5], scope='session') metafunc.parametrize("fixture2", [4, 5], scope='session')
@pytest.fixture(scope='session') @pytest.fixture(scope='session')
def fixture4(): def fixture2(fixture3):
pass
@pytest.fixture(scope='session')
def fixture2(fixture3, fixture4):
pass pass
def test(fixture2): def test(fixture2):
@ -4575,6 +4576,8 @@ def test_fixture_info_after_dynamic_parametrize(pytester: Pytester) -> None:
def test_reordering_after_dynamic_parametrize(pytester: Pytester): def test_reordering_after_dynamic_parametrize(pytester: Pytester):
"""Make sure that prunning dependency tree takes place correctly, regarding from
reordering's viewpoint."""
pytester.makepyfile( pytester.makepyfile(
""" """
import pytest import pytest
@ -4583,7 +4586,7 @@ def test_reordering_after_dynamic_parametrize(pytester: Pytester):
if metafunc.definition.name == "test_0": if metafunc.definition.name == "test_0":
metafunc.parametrize("fixture2", [0]) metafunc.parametrize("fixture2", [0])
@pytest.fixture(scope='module') @pytest.fixture(scope='module', params=[0])
def fixture1(): def fixture1():
pass pass
@ -4615,6 +4618,9 @@ def test_reordering_after_dynamic_parametrize(pytester: Pytester):
def test_request_shouldnt_be_in_closure_after_pruning_dep_tree_when_its_not_in_initial_closure( def test_request_shouldnt_be_in_closure_after_pruning_dep_tree_when_its_not_in_initial_closure(
pytester: Pytester, pytester: Pytester,
): ):
"""Make sure that fixture `request` doesn't show up in the closure after prunning dependency
tree when it has not been there beforehand.
"""
pytester.makepyfile( pytester.makepyfile(
""" """
import pytest import pytest
@ -4641,6 +4647,10 @@ def test_request_shouldnt_be_in_closure_after_pruning_dep_tree_when_its_not_in_i
def test_dont_recompute_dependency_tree_if_no_direct_dynamic_parametrize( def test_dont_recompute_dependency_tree_if_no_direct_dynamic_parametrize(
pytester: Pytester, pytester: Pytester,
): ):
"""We should not update item's dependency tree when there's no direct dynamic
parametrization, i.e. `metafunc.parametrize(indirect=False)`s in module/class specific
`pytest_generate_tests` hooks, for the sake of efficiency.
"""
pytester.makeconftest( pytester.makeconftest(
""" """
import pytest import pytest