Merge branch 'main' into Improvement-catch-duplicate-values-when-determining-param-indices-in-metafunc-parametrize
This commit is contained in:
commit
da499bbd23
|
@ -29,7 +29,7 @@ repos:
|
||||||
language: python
|
language: python
|
||||||
files: \.py$
|
files: \.py$
|
||||||
- repo: https://github.com/PyCQA/flake8
|
- repo: https://github.com/PyCQA/flake8
|
||||||
rev: 6.0.0
|
rev: 6.1.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
language_version: python3
|
language_version: python3
|
||||||
|
@ -42,7 +42,7 @@ repos:
|
||||||
- id: reorder-python-imports
|
- id: reorder-python-imports
|
||||||
args: ['--application-directories=.:src', --py38-plus]
|
args: ['--application-directories=.:src', --py38-plus]
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v3.9.0
|
rev: v3.10.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args: [--py38-plus]
|
args: [--py38-plus]
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Fixed a bug that when there are multiple fixtures for an indirect parameter,
|
||||||
|
the scope of the highest-scope fixture is picked for the parameter set, instead of that of the one with the narrowest scope.
|
|
@ -657,13 +657,16 @@ Use :func:`pytest.raises` with the
|
||||||
:ref:`pytest.mark.parametrize ref` decorator to write parametrized tests
|
:ref:`pytest.mark.parametrize ref` decorator to write parametrized tests
|
||||||
in which some tests raise exceptions and others do not.
|
in which some tests raise exceptions and others do not.
|
||||||
|
|
||||||
It may be helpful to use ``nullcontext`` as a complement to ``raises``.
|
``contextlib.nullcontext`` can be used to test cases that are not expected to
|
||||||
|
raise exceptions but that should result in some value. The value is given as the
|
||||||
|
``enter_result`` parameter, which will be available as the ``with`` statement’s
|
||||||
|
target (``e`` in the example below).
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from contextlib import nullcontext as does_not_raise
|
from contextlib import nullcontext
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -671,16 +674,17 @@ For example:
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"example_input,expectation",
|
"example_input,expectation",
|
||||||
[
|
[
|
||||||
(3, does_not_raise()),
|
(3, nullcontext(2)),
|
||||||
(2, does_not_raise()),
|
(2, nullcontext(3)),
|
||||||
(1, does_not_raise()),
|
(1, nullcontext(6)),
|
||||||
(0, pytest.raises(ZeroDivisionError)),
|
(0, pytest.raises(ZeroDivisionError)),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_division(example_input, expectation):
|
def test_division(example_input, expectation):
|
||||||
"""Test how much I know division."""
|
"""Test how much I know division."""
|
||||||
with expectation:
|
with expectation as e:
|
||||||
assert (6 / example_input) is not None
|
assert (6 / example_input) == e
|
||||||
|
|
||||||
In the example above, the first three test cases should run unexceptionally,
|
In the example above, the first three test cases should run without any
|
||||||
while the fourth should raise ``ZeroDivisionError``.
|
exceptions, while the fourth should raise a``ZeroDivisionError`` exception,
|
||||||
|
which is expected by pytest.
|
||||||
|
|
|
@ -54,14 +54,13 @@ operators. (See :ref:`tbreportdemo`). This allows you to use the
|
||||||
idiomatic python constructs without boilerplate code while not losing
|
idiomatic python constructs without boilerplate code while not losing
|
||||||
introspection information.
|
introspection information.
|
||||||
|
|
||||||
However, if you specify a message with the assertion like this:
|
If a message is specified with the assertion like this:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
assert a % 2 == 0, "value was odd, should be even"
|
assert a % 2 == 0, "value was odd, should be even"
|
||||||
|
|
||||||
then no assertion introspection takes places at all and the message
|
it is printed alongside the assertion introspection in the traceback.
|
||||||
will be simply shown in the traceback.
|
|
||||||
|
|
||||||
See :ref:`assert-details` for more information on assertion introspection.
|
See :ref:`assert-details` for more information on assertion introspection.
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,8 @@ pytest.exit
|
||||||
pytest.main
|
pytest.main
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
**Tutorial**: :ref:`pytest.main-usage`
|
||||||
|
|
||||||
.. autofunction:: pytest.main
|
.. autofunction:: pytest.main
|
||||||
|
|
||||||
pytest.param
|
pytest.param
|
||||||
|
|
|
@ -222,7 +222,7 @@ def _compare_eq_any(left: Any, right: Any, verbose: int = 0) -> List[str]:
|
||||||
other_side = right if isinstance(left, ApproxBase) else left
|
other_side = right if isinstance(left, ApproxBase) else left
|
||||||
|
|
||||||
explanation = approx_side._repr_compare(other_side)
|
explanation = approx_side._repr_compare(other_side)
|
||||||
elif type(left) == type(right) and (
|
elif type(left) is type(right) and (
|
||||||
isdatacls(left) or isattrs(left) or isnamedtuple(left)
|
isdatacls(left) or isattrs(left) or isnamedtuple(left)
|
||||||
):
|
):
|
||||||
# Note: unlike dataclasses/attrs, namedtuples compare only the
|
# Note: unlike dataclasses/attrs, namedtuples compare only the
|
||||||
|
|
|
@ -581,26 +581,25 @@ class PytestPluginManager(PluginManager):
|
||||||
def _try_load_conftest(
|
def _try_load_conftest(
|
||||||
self, anchor: Path, importmode: Union[str, ImportMode], rootpath: Path
|
self, anchor: Path, importmode: Union[str, ImportMode], rootpath: Path
|
||||||
) -> None:
|
) -> None:
|
||||||
self._getconftestmodules(anchor, importmode, rootpath)
|
self._loadconftestmodules(anchor, importmode, rootpath)
|
||||||
# let's also consider test* subdirs
|
# let's also consider test* subdirs
|
||||||
if anchor.is_dir():
|
if anchor.is_dir():
|
||||||
for x in anchor.glob("test*"):
|
for x in anchor.glob("test*"):
|
||||||
if x.is_dir():
|
if x.is_dir():
|
||||||
self._getconftestmodules(x, importmode, rootpath)
|
self._loadconftestmodules(x, importmode, rootpath)
|
||||||
|
|
||||||
def _getconftestmodules(
|
def _loadconftestmodules(
|
||||||
self, path: Path, importmode: Union[str, ImportMode], rootpath: Path
|
self, path: Path, importmode: Union[str, ImportMode], rootpath: Path
|
||||||
) -> Sequence[types.ModuleType]:
|
) -> None:
|
||||||
if self._noconftest:
|
if self._noconftest:
|
||||||
return []
|
return
|
||||||
|
|
||||||
directory = self._get_directory(path)
|
directory = self._get_directory(path)
|
||||||
|
|
||||||
# Optimization: avoid repeated searches in the same directory.
|
# Optimization: avoid repeated searches in the same directory.
|
||||||
# Assumes always called with same importmode and rootpath.
|
# Assumes always called with same importmode and rootpath.
|
||||||
existing_clist = self._dirpath2confmods.get(directory)
|
if directory in self._dirpath2confmods:
|
||||||
if existing_clist is not None:
|
return
|
||||||
return existing_clist
|
|
||||||
|
|
||||||
# XXX these days we may rather want to use config.rootpath
|
# XXX these days we may rather want to use config.rootpath
|
||||||
# and allow users to opt into looking into the rootdir parent
|
# and allow users to opt into looking into the rootdir parent
|
||||||
|
@ -613,16 +612,17 @@ class PytestPluginManager(PluginManager):
|
||||||
mod = self._importconftest(conftestpath, importmode, rootpath)
|
mod = self._importconftest(conftestpath, importmode, rootpath)
|
||||||
clist.append(mod)
|
clist.append(mod)
|
||||||
self._dirpath2confmods[directory] = clist
|
self._dirpath2confmods[directory] = clist
|
||||||
return clist
|
|
||||||
|
def _getconftestmodules(self, path: Path) -> Sequence[types.ModuleType]:
|
||||||
|
directory = self._get_directory(path)
|
||||||
|
return self._dirpath2confmods.get(directory, ())
|
||||||
|
|
||||||
def _rget_with_confmod(
|
def _rget_with_confmod(
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
path: Path,
|
path: Path,
|
||||||
importmode: Union[str, ImportMode],
|
|
||||||
rootpath: Path,
|
|
||||||
) -> Tuple[types.ModuleType, Any]:
|
) -> Tuple[types.ModuleType, Any]:
|
||||||
modules = self._getconftestmodules(path, importmode, rootpath=rootpath)
|
modules = self._getconftestmodules(path)
|
||||||
for mod in reversed(modules):
|
for mod in reversed(modules):
|
||||||
try:
|
try:
|
||||||
return mod, getattr(mod, name)
|
return mod, getattr(mod, name)
|
||||||
|
@ -1562,13 +1562,9 @@ class Config:
|
||||||
else:
|
else:
|
||||||
return self._getini_unknown_type(name, type, value)
|
return self._getini_unknown_type(name, type, value)
|
||||||
|
|
||||||
def _getconftest_pathlist(
|
def _getconftest_pathlist(self, name: str, path: Path) -> Optional[List[Path]]:
|
||||||
self, name: str, path: Path, rootpath: Path
|
|
||||||
) -> Optional[List[Path]]:
|
|
||||||
try:
|
try:
|
||||||
mod, relroots = self.pluginmanager._rget_with_confmod(
|
mod, relroots = self.pluginmanager._rget_with_confmod(name, path)
|
||||||
name, path, self.getoption("importmode"), rootpath
|
|
||||||
)
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
return None
|
||||||
assert mod.__file__ is not None
|
assert mod.__file__ is not None
|
||||||
|
|
|
@ -155,11 +155,17 @@ def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Parametrized fixture key, helper alias for code below.
|
@dataclasses.dataclass(frozen=True)
|
||||||
_Key = Tuple[object, ...]
|
class FixtureArgKey:
|
||||||
|
argname: str
|
||||||
|
param_index: int
|
||||||
|
scoped_item_path: Optional[Path]
|
||||||
|
item_cls: Optional[type]
|
||||||
|
|
||||||
|
|
||||||
def get_parametrized_fixture_keys(item: nodes.Item, scope: Scope) -> Iterator[_Key]:
|
def get_parametrized_fixture_keys(
|
||||||
|
item: nodes.Item, scope: Scope
|
||||||
|
) -> Iterator[FixtureArgKey]:
|
||||||
"""Return list of keys for all parametrized arguments which match
|
"""Return list of keys for all parametrized arguments which match
|
||||||
the specified scope."""
|
the specified scope."""
|
||||||
assert scope is not Scope.Function
|
assert scope is not Scope.Function
|
||||||
|
@ -169,24 +175,28 @@ def get_parametrized_fixture_keys(item: nodes.Item, scope: Scope) -> Iterator[_K
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
cs: CallSpec2 = callspec
|
cs: CallSpec2 = callspec
|
||||||
# cs.indices.items() is random order of argnames. Need to
|
# cs.indices is random order of argnames. Need to
|
||||||
# sort this so that different calls to
|
# sort this so that different calls to
|
||||||
# get_parametrized_fixture_keys will be deterministic.
|
# get_parametrized_fixture_keys will be deterministic.
|
||||||
for argname, param_index in sorted(cs.indices.items()):
|
for argname in sorted(cs.indices):
|
||||||
if cs._arg2scope[argname] != scope:
|
if cs._arg2scope[argname] != scope:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
item_cls = None
|
||||||
if scope is Scope.Session:
|
if scope is Scope.Session:
|
||||||
key: _Key = (argname, param_index)
|
scoped_item_path = None
|
||||||
elif scope is Scope.Package:
|
elif scope is Scope.Package:
|
||||||
key = (argname, param_index, item.path)
|
scoped_item_path = item.path
|
||||||
elif scope is Scope.Module:
|
elif scope is Scope.Module:
|
||||||
key = (argname, param_index, item.path)
|
scoped_item_path = item.path
|
||||||
elif scope is Scope.Class:
|
elif scope is Scope.Class:
|
||||||
|
scoped_item_path = item.path
|
||||||
item_cls = item.cls # type: ignore[attr-defined]
|
item_cls = item.cls # type: ignore[attr-defined]
|
||||||
key = (argname, param_index, item.path, item_cls)
|
|
||||||
else:
|
else:
|
||||||
assert_never(scope)
|
assert_never(scope)
|
||||||
yield key
|
|
||||||
|
param_index = cs.indices[argname]
|
||||||
|
yield FixtureArgKey(argname, param_index, scoped_item_path, item_cls)
|
||||||
|
|
||||||
|
|
||||||
# Algorithm for sorting on a per-parametrized resource setup basis.
|
# Algorithm for sorting on a per-parametrized resource setup basis.
|
||||||
|
@ -196,12 +206,12 @@ def get_parametrized_fixture_keys(item: nodes.Item, scope: Scope) -> Iterator[_K
|
||||||
|
|
||||||
|
|
||||||
def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]:
|
def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]:
|
||||||
argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[_Key, None]]] = {}
|
argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]] = {}
|
||||||
items_by_argkey: Dict[Scope, Dict[_Key, Deque[nodes.Item]]] = {}
|
items_by_argkey: Dict[Scope, Dict[FixtureArgKey, Deque[nodes.Item]]] = {}
|
||||||
for scope in HIGH_SCOPES:
|
for scope in HIGH_SCOPES:
|
||||||
d: Dict[nodes.Item, Dict[_Key, None]] = {}
|
d: Dict[nodes.Item, Dict[FixtureArgKey, None]] = {}
|
||||||
argkeys_cache[scope] = d
|
argkeys_cache[scope] = d
|
||||||
item_d: Dict[_Key, Deque[nodes.Item]] = defaultdict(deque)
|
item_d: Dict[FixtureArgKey, Deque[nodes.Item]] = defaultdict(deque)
|
||||||
items_by_argkey[scope] = item_d
|
items_by_argkey[scope] = item_d
|
||||||
for item in items:
|
for item in items:
|
||||||
keys = dict.fromkeys(get_parametrized_fixture_keys(item, scope), None)
|
keys = dict.fromkeys(get_parametrized_fixture_keys(item, scope), None)
|
||||||
|
@ -217,8 +227,8 @@ def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]:
|
||||||
|
|
||||||
def fix_cache_order(
|
def fix_cache_order(
|
||||||
item: nodes.Item,
|
item: nodes.Item,
|
||||||
argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[_Key, None]]],
|
argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]],
|
||||||
items_by_argkey: Dict[Scope, Dict[_Key, "Deque[nodes.Item]"]],
|
items_by_argkey: Dict[Scope, Dict[FixtureArgKey, "Deque[nodes.Item]"]],
|
||||||
) -> None:
|
) -> None:
|
||||||
for scope in HIGH_SCOPES:
|
for scope in HIGH_SCOPES:
|
||||||
for key in argkeys_cache[scope].get(item, []):
|
for key in argkeys_cache[scope].get(item, []):
|
||||||
|
@ -227,13 +237,13 @@ def fix_cache_order(
|
||||||
|
|
||||||
def reorder_items_atscope(
|
def reorder_items_atscope(
|
||||||
items: Dict[nodes.Item, None],
|
items: Dict[nodes.Item, None],
|
||||||
argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[_Key, None]]],
|
argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]],
|
||||||
items_by_argkey: Dict[Scope, Dict[_Key, "Deque[nodes.Item]"]],
|
items_by_argkey: Dict[Scope, Dict[FixtureArgKey, "Deque[nodes.Item]"]],
|
||||||
scope: Scope,
|
scope: Scope,
|
||||||
) -> Dict[nodes.Item, None]:
|
) -> Dict[nodes.Item, None]:
|
||||||
if scope is Scope.Function or len(items) < 3:
|
if scope is Scope.Function or len(items) < 3:
|
||||||
return items
|
return items
|
||||||
ignore: Set[Optional[_Key]] = set()
|
ignore: Set[Optional[FixtureArgKey]] = set()
|
||||||
items_deque = deque(items)
|
items_deque = deque(items)
|
||||||
items_done: Dict[nodes.Item, None] = {}
|
items_done: Dict[nodes.Item, None] = {}
|
||||||
scoped_items_by_argkey = items_by_argkey[scope]
|
scoped_items_by_argkey = items_by_argkey[scope]
|
||||||
|
@ -394,7 +404,7 @@ class FixtureRequest:
|
||||||
node: Optional[Union[nodes.Item, nodes.Collector]] = self._pyfuncitem
|
node: Optional[Union[nodes.Item, nodes.Collector]] = self._pyfuncitem
|
||||||
elif scope is Scope.Package:
|
elif scope is Scope.Package:
|
||||||
# FIXME: _fixturedef is not defined on FixtureRequest (this class),
|
# FIXME: _fixturedef is not defined on FixtureRequest (this class),
|
||||||
# but on FixtureRequest (a subclass).
|
# but on SubRequest (a subclass).
|
||||||
node = get_scope_package(self._pyfuncitem, self._fixturedef) # type: ignore[attr-defined]
|
node = get_scope_package(self._pyfuncitem, self._fixturedef) # type: ignore[attr-defined]
|
||||||
else:
|
else:
|
||||||
node = get_scope_node(self._pyfuncitem, scope)
|
node = get_scope_node(self._pyfuncitem, scope)
|
||||||
|
|
|
@ -376,7 +376,7 @@ def _in_venv(path: Path) -> bool:
|
||||||
|
|
||||||
def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[bool]:
|
def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[bool]:
|
||||||
ignore_paths = config._getconftest_pathlist(
|
ignore_paths = config._getconftest_pathlist(
|
||||||
"collect_ignore", path=collection_path.parent, rootpath=config.rootpath
|
"collect_ignore", path=collection_path.parent
|
||||||
)
|
)
|
||||||
ignore_paths = ignore_paths or []
|
ignore_paths = ignore_paths or []
|
||||||
excludeopt = config.getoption("ignore")
|
excludeopt = config.getoption("ignore")
|
||||||
|
@ -387,7 +387,7 @@ def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[boo
|
||||||
return True
|
return True
|
||||||
|
|
||||||
ignore_globs = config._getconftest_pathlist(
|
ignore_globs = config._getconftest_pathlist(
|
||||||
"collect_ignore_glob", path=collection_path.parent, rootpath=config.rootpath
|
"collect_ignore_glob", path=collection_path.parent
|
||||||
)
|
)
|
||||||
ignore_globs = ignore_globs or []
|
ignore_globs = ignore_globs or []
|
||||||
excludeglobopt = config.getoption("ignore_glob")
|
excludeglobopt = config.getoption("ignore_glob")
|
||||||
|
@ -551,11 +551,16 @@ class Session(nodes.FSCollector):
|
||||||
pm = self.config.pluginmanager
|
pm = self.config.pluginmanager
|
||||||
# Check if we have the common case of running
|
# Check if we have the common case of running
|
||||||
# hooks with all conftest.py files.
|
# hooks with all conftest.py files.
|
||||||
my_conftestmodules = pm._getconftestmodules(
|
#
|
||||||
|
# TODO: pytest relies on this call to load non-initial conftests. This
|
||||||
|
# is incidental. It will be better to load conftests at a more
|
||||||
|
# well-defined place.
|
||||||
|
pm._loadconftestmodules(
|
||||||
path,
|
path,
|
||||||
self.config.getoption("importmode"),
|
self.config.getoption("importmode"),
|
||||||
rootpath=self.config.rootpath,
|
rootpath=self.config.rootpath,
|
||||||
)
|
)
|
||||||
|
my_conftestmodules = pm._getconftestmodules(path)
|
||||||
remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
|
remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
|
||||||
if remove_mods:
|
if remove_mods:
|
||||||
# One or more conftests are not in use at this fspath.
|
# One or more conftests are not in use at this fspath.
|
||||||
|
|
|
@ -41,7 +41,6 @@ from _pytest._code.code import Traceback
|
||||||
from _pytest._io import TerminalWriter
|
from _pytest._io import TerminalWriter
|
||||||
from _pytest._io.saferepr import saferepr
|
from _pytest._io.saferepr import saferepr
|
||||||
from _pytest.compat import ascii_escaped
|
from _pytest.compat import ascii_escaped
|
||||||
from _pytest.compat import assert_never
|
|
||||||
from _pytest.compat import get_default_arg_names
|
from _pytest.compat import get_default_arg_names
|
||||||
from _pytest.compat import get_real_func
|
from _pytest.compat import get_real_func
|
||||||
from _pytest.compat import getimfunc
|
from _pytest.compat import getimfunc
|
||||||
|
@ -498,8 +497,11 @@ class PyCollector(PyobjMixin, nodes.Collector):
|
||||||
if not metafunc._calls:
|
if not metafunc._calls:
|
||||||
yield Function.from_parent(self, name=name, fixtureinfo=fixtureinfo)
|
yield Function.from_parent(self, name=name, fixtureinfo=fixtureinfo)
|
||||||
else:
|
else:
|
||||||
# Dynamic direct parametrization may have shadowed some fixtures,
|
# Direct parametrizations taking place in module/class-specific
|
||||||
# so make sure we update what the function really needs.
|
# `metafunc.parametrize` calls may have shadowed some fixtures, so make sure
|
||||||
|
# we update what the function really needs a.k.a its fixture closure. Note that
|
||||||
|
# direct parametrizations using `@pytest.mark.parametrize` have already been considered
|
||||||
|
# into making the closure using `ignore_args` arg to `getfixtureclosure`.
|
||||||
fixtureinfo.prune_dependency_tree()
|
fixtureinfo.prune_dependency_tree()
|
||||||
|
|
||||||
for callspec in metafunc._calls:
|
for callspec in metafunc._calls:
|
||||||
|
@ -1283,8 +1285,9 @@ class Metafunc:
|
||||||
during the collection phase. If you need to setup expensive resources
|
during the collection phase. If you need to setup expensive resources
|
||||||
see about setting indirect to do it rather than at test setup time.
|
see about setting indirect to do it rather than at test setup time.
|
||||||
|
|
||||||
Can be called multiple times, in which case each call parametrizes all
|
Can be called multiple times per test function (but only on different
|
||||||
previous parametrizations, e.g.
|
argument names), in which case each call parametrizes all previous
|
||||||
|
parametrizations, e.g.
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
|
@ -1389,7 +1392,8 @@ class Metafunc:
|
||||||
# a per-scope basis. We thus store and cache the fixturedef on the
|
# a per-scope basis. We thus store and cache the fixturedef on the
|
||||||
# node related to the scope.
|
# node related to the scope.
|
||||||
if scope_ is not Scope.Function:
|
if scope_ is not Scope.Function:
|
||||||
collector = cast(nodes.Node, self.definition.parent)
|
collector = self.definition.parent
|
||||||
|
assert collector is not None
|
||||||
node = get_scope_node(collector, scope_)
|
node = get_scope_node(collector, scope_)
|
||||||
if node is None:
|
if node is None:
|
||||||
# If used class scope and there is no class, use module-level
|
# If used class scope and there is no class, use module-level
|
||||||
|
@ -1402,7 +1406,7 @@ class Metafunc:
|
||||||
elif scope_ is Scope.Package:
|
elif scope_ is Scope.Package:
|
||||||
node = collector.session
|
node = collector.session
|
||||||
else:
|
else:
|
||||||
assert_never(scope_) # type: ignore[arg-type]
|
assert False, f"Unhandled missing scope: {scope}"
|
||||||
if node is None:
|
if node is None:
|
||||||
name2pseudofixturedef = None
|
name2pseudofixturedef = None
|
||||||
else:
|
else:
|
||||||
|
@ -1410,9 +1414,9 @@ class Metafunc:
|
||||||
name2pseudofixturedef = node.stash.setdefault(
|
name2pseudofixturedef = node.stash.setdefault(
|
||||||
name2pseudofixturedef_key, default
|
name2pseudofixturedef_key, default
|
||||||
)
|
)
|
||||||
arg_values_types = self._resolve_arg_value_types(argnames, indirect)
|
arg_directness = self._resolve_args_directness(argnames, indirect)
|
||||||
for argname in argnames:
|
for argname in argnames:
|
||||||
if arg_values_types[argname] == "params":
|
if arg_directness[argname] == "indirect":
|
||||||
continue
|
continue
|
||||||
if name2pseudofixturedef is not None and argname in name2pseudofixturedef:
|
if name2pseudofixturedef is not None and argname in name2pseudofixturedef:
|
||||||
fixturedef = name2pseudofixturedef[argname]
|
fixturedef = name2pseudofixturedef[argname]
|
||||||
|
@ -1517,28 +1521,30 @@ class Metafunc:
|
||||||
|
|
||||||
return list(itertools.islice(ids, num_ids))
|
return list(itertools.islice(ids, num_ids))
|
||||||
|
|
||||||
def _resolve_arg_value_types(
|
def _resolve_args_directness(
|
||||||
self,
|
self,
|
||||||
argnames: Sequence[str],
|
argnames: Sequence[str],
|
||||||
indirect: Union[bool, Sequence[str]],
|
indirect: Union[bool, Sequence[str]],
|
||||||
) -> Dict[str, "Literal['params', 'funcargs']"]:
|
) -> Dict[str, Literal["indirect", "direct"]]:
|
||||||
"""Resolve if each parametrized argument must be considered a
|
"""Resolve if each parametrized argument must be considered an indirect
|
||||||
parameter to a fixture or a "funcarg" to the function, based on the
|
parameter to a fixture of the same name, or a direct parameter to the
|
||||||
``indirect`` parameter of the parametrized() call.
|
parametrized function, based on the ``indirect`` parameter of the
|
||||||
|
parametrized() call.
|
||||||
|
|
||||||
:param List[str] argnames: List of argument names passed to ``parametrize()``.
|
:param argnames:
|
||||||
:param indirect: Same as the ``indirect`` parameter of ``parametrize()``.
|
List of argument names passed to ``parametrize()``.
|
||||||
:rtype: Dict[str, str]
|
:param indirect:
|
||||||
A dict mapping each arg name to either:
|
Same as the ``indirect`` parameter of ``parametrize()``.
|
||||||
* "params" if the argname should be the parameter of a fixture of the same name.
|
:returns
|
||||||
* "funcargs" if the argname should be a parameter to the parametrized test function.
|
A dict mapping each arg name to either "indirect" or "direct".
|
||||||
"""
|
"""
|
||||||
|
arg_directness: Dict[str, Literal["indirect", "direct"]]
|
||||||
if isinstance(indirect, bool):
|
if isinstance(indirect, bool):
|
||||||
valtypes: Dict[str, Literal["params", "funcargs"]] = dict.fromkeys(
|
arg_directness = dict.fromkeys(
|
||||||
argnames, "params" if indirect else "funcargs"
|
argnames, "indirect" if indirect else "direct"
|
||||||
)
|
)
|
||||||
elif isinstance(indirect, Sequence):
|
elif isinstance(indirect, Sequence):
|
||||||
valtypes = dict.fromkeys(argnames, "funcargs")
|
arg_directness = dict.fromkeys(argnames, "direct")
|
||||||
for arg in indirect:
|
for arg in indirect:
|
||||||
if arg not in argnames:
|
if arg not in argnames:
|
||||||
fail(
|
fail(
|
||||||
|
@ -1547,7 +1553,7 @@ class Metafunc:
|
||||||
),
|
),
|
||||||
pytrace=False,
|
pytrace=False,
|
||||||
)
|
)
|
||||||
valtypes[arg] = "params"
|
arg_directness[arg] = "indirect"
|
||||||
else:
|
else:
|
||||||
fail(
|
fail(
|
||||||
"In {func}: expected Sequence or boolean for indirect, got {type}".format(
|
"In {func}: expected Sequence or boolean for indirect, got {type}".format(
|
||||||
|
@ -1555,7 +1561,7 @@ class Metafunc:
|
||||||
),
|
),
|
||||||
pytrace=False,
|
pytrace=False,
|
||||||
)
|
)
|
||||||
return valtypes
|
return arg_directness
|
||||||
|
|
||||||
def _validate_if_using_arg_names(
|
def _validate_if_using_arg_names(
|
||||||
self,
|
self,
|
||||||
|
@ -1612,7 +1618,7 @@ def _find_parametrized_scope(
|
||||||
if all_arguments_are_fixtures:
|
if all_arguments_are_fixtures:
|
||||||
fixturedefs = arg2fixturedefs or {}
|
fixturedefs = arg2fixturedefs or {}
|
||||||
used_scopes = [
|
used_scopes = [
|
||||||
fixturedef[0]._scope
|
fixturedef[-1]._scope
|
||||||
for name, fixturedef in fixturedefs.items()
|
for name, fixturedef in fixturedefs.items()
|
||||||
if name in argnames
|
if name in argnames
|
||||||
]
|
]
|
||||||
|
@ -1778,7 +1784,7 @@ class Function(PyobjMixin, nodes.Item):
|
||||||
:param config:
|
:param config:
|
||||||
The pytest Config object.
|
The pytest Config object.
|
||||||
:param callspec:
|
:param callspec:
|
||||||
If given, this is function has been parametrized and the callspec contains
|
If given, this function has been parametrized and the callspec contains
|
||||||
meta information about the parametrization.
|
meta information about the parametrization.
|
||||||
:param callobj:
|
:param callobj:
|
||||||
If given, the object which will be called when the Function is invoked,
|
If given, the object which will be called when the Function is invoked,
|
||||||
|
|
|
@ -1573,4 +1573,4 @@ class TestBinaryAndTextMethods:
|
||||||
x.write_text(part, "ascii")
|
x.write_text(part, "ascii")
|
||||||
s = x.read_text("ascii")
|
s = x.read_text("ascii")
|
||||||
assert s == part
|
assert s == part
|
||||||
assert type(s) == type(part)
|
assert type(s) is type(part)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
anyio[curio,trio]==3.7.1
|
anyio[curio,trio]==3.7.1
|
||||||
django==4.2.3
|
django==4.2.4
|
||||||
pytest-asyncio==0.21.1
|
pytest-asyncio==0.21.1
|
||||||
pytest-bdd==6.1.1
|
pytest-bdd==6.1.1
|
||||||
pytest-cov==4.1.0
|
pytest-cov==4.1.0
|
||||||
|
|
|
@ -2103,9 +2103,7 @@ class TestAutouseManagement:
|
||||||
reprec = pytester.inline_run("-v", "-s", "--confcutdir", pytester.path)
|
reprec = pytester.inline_run("-v", "-s", "--confcutdir", pytester.path)
|
||||||
reprec.assertoutcome(passed=8)
|
reprec.assertoutcome(passed=8)
|
||||||
config = reprec.getcalls("pytest_unconfigure")[0].config
|
config = reprec.getcalls("pytest_unconfigure")[0].config
|
||||||
values = config.pluginmanager._getconftestmodules(
|
values = config.pluginmanager._getconftestmodules(p)[0].values
|
||||||
p, importmode="prepend", rootpath=pytester.path
|
|
||||||
)[0].values
|
|
||||||
assert values == ["fin_a1", "fin_a2", "fin_b1", "fin_b2"] * 2
|
assert values == ["fin_a1", "fin_a2", "fin_b1", "fin_b2"] * 2
|
||||||
|
|
||||||
def test_scope_ordering(self, pytester: Pytester) -> None:
|
def test_scope_ordering(self, pytester: Pytester) -> None:
|
||||||
|
|
|
@ -23,6 +23,7 @@ from _pytest.compat import getfuncargnames
|
||||||
from _pytest.compat import NOTSET
|
from _pytest.compat import NOTSET
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
from _pytest.pytester import Pytester
|
from _pytest.pytester import Pytester
|
||||||
|
from _pytest.python import Function
|
||||||
from _pytest.python import IdMaker
|
from _pytest.python import IdMaker
|
||||||
from _pytest.scope import Scope
|
from _pytest.scope import Scope
|
||||||
|
|
||||||
|
@ -161,6 +162,7 @@ class TestMetafunc:
|
||||||
module_fix=[DummyFixtureDef(Scope.Module)],
|
module_fix=[DummyFixtureDef(Scope.Module)],
|
||||||
class_fix=[DummyFixtureDef(Scope.Class)],
|
class_fix=[DummyFixtureDef(Scope.Class)],
|
||||||
func_fix=[DummyFixtureDef(Scope.Function)],
|
func_fix=[DummyFixtureDef(Scope.Function)],
|
||||||
|
mixed_fix=[DummyFixtureDef(Scope.Module), DummyFixtureDef(Scope.Class)],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -197,6 +199,7 @@ class TestMetafunc:
|
||||||
)
|
)
|
||||||
== Scope.Module
|
== Scope.Module
|
||||||
)
|
)
|
||||||
|
assert find_scope(["mixed_fix"], indirect=True) == Scope.Class
|
||||||
|
|
||||||
def test_parametrize_and_id(self) -> None:
|
def test_parametrize_and_id(self) -> None:
|
||||||
def func(x, y):
|
def func(x, y):
|
||||||
|
@ -1588,29 +1591,94 @@ class TestMetafuncFunctional:
|
||||||
def test_parametrize_module_level_test_with_class_scope(
|
def test_parametrize_module_level_test_with_class_scope(
|
||||||
self, pytester: Pytester
|
self, pytester: Pytester
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""
|
||||||
|
Test that a class-scoped parametrization without a corresponding `Class`
|
||||||
|
gets module scope, i.e. we only create a single FixtureDef for it per module.
|
||||||
|
"""
|
||||||
|
module = pytester.makepyfile(
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("x", [0, 1], scope="class")
|
||||||
|
def test_1(x):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("x", [1, 2], scope="module")
|
||||||
|
def test_2(x):
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
test_1_0, _, test_2_0, _ = pytester.genitems((pytester.getmodulecol(module),))
|
||||||
|
|
||||||
|
assert isinstance(test_1_0, Function)
|
||||||
|
assert test_1_0.name == "test_1[0]"
|
||||||
|
test_1_fixture_x = test_1_0._fixtureinfo.name2fixturedefs["x"][-1]
|
||||||
|
|
||||||
|
assert isinstance(test_2_0, Function)
|
||||||
|
assert test_2_0.name == "test_2[1]"
|
||||||
|
test_2_fixture_x = test_2_0._fixtureinfo.name2fixturedefs["x"][-1]
|
||||||
|
|
||||||
|
assert test_1_fixture_x is test_2_fixture_x
|
||||||
|
|
||||||
|
def test_reordering_with_scopeless_and_just_indirect_parametrization(
|
||||||
|
self, pytester: Pytester
|
||||||
|
) -> None:
|
||||||
|
pytester.makeconftest(
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.fixture(scope="package")
|
||||||
|
def fixture1():
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
)
|
||||||
pytester.makepyfile(
|
pytester.makepyfile(
|
||||||
"""
|
"""
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture(scope="module")
|
||||||
def item(request):
|
def fixture0():
|
||||||
return request._pyfuncitem
|
pass
|
||||||
|
|
||||||
fixturedef = None
|
@pytest.fixture(scope="module")
|
||||||
|
def fixture1(fixture0):
|
||||||
|
pass
|
||||||
|
|
||||||
@pytest.mark.parametrize("x", [0, 1], scope="class")
|
@pytest.mark.parametrize("fixture1", [0], indirect=True)
|
||||||
def test_1(item, x):
|
def test_0(fixture1):
|
||||||
global fixturedef
|
pass
|
||||||
fixturedef = item._fixtureinfo.name2fixturedefs['x'][-1]
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("x", [1, 2], scope="module")
|
@pytest.fixture(scope="module")
|
||||||
def test_2(item, x):
|
def fixture():
|
||||||
global fixturedef
|
pass
|
||||||
assert fixturedef == item._fixtureinfo.name2fixturedefs['x'][-1]
|
|
||||||
|
@pytest.mark.parametrize("fixture", [0], indirect=True)
|
||||||
|
def test_1(fixture):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_2():
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Test:
|
||||||
|
@pytest.fixture(scope="class")
|
||||||
|
def fixture(self, fixture):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("fixture", [0], indirect=True)
|
||||||
|
def test_3(self, fixture):
|
||||||
|
pass
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
result = pytester.runpytest()
|
result = pytester.runpytest("-v")
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
|
result.stdout.fnmatch_lines(
|
||||||
|
[
|
||||||
|
"*test_0*",
|
||||||
|
"*test_1*",
|
||||||
|
"*test_2*",
|
||||||
|
"*test_3*",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestMetafuncFunctionalAuto:
|
class TestMetafuncFunctionalAuto:
|
||||||
|
|
|
@ -642,18 +642,11 @@ class TestConfigAPI:
|
||||||
p = tmp_path.joinpath("conftest.py")
|
p = tmp_path.joinpath("conftest.py")
|
||||||
p.write_text(f"mylist = {['.', str(somepath)]}", encoding="utf-8")
|
p.write_text(f"mylist = {['.', str(somepath)]}", encoding="utf-8")
|
||||||
config = pytester.parseconfigure(p)
|
config = pytester.parseconfigure(p)
|
||||||
assert (
|
assert config._getconftest_pathlist("notexist", path=tmp_path) is None
|
||||||
config._getconftest_pathlist("notexist", path=tmp_path, rootpath=tmp_path)
|
assert config._getconftest_pathlist("mylist", path=tmp_path) == [
|
||||||
is None
|
tmp_path,
|
||||||
)
|
somepath,
|
||||||
pl = (
|
]
|
||||||
config._getconftest_pathlist("mylist", path=tmp_path, rootpath=tmp_path)
|
|
||||||
or []
|
|
||||||
)
|
|
||||||
print(pl)
|
|
||||||
assert len(pl) == 2
|
|
||||||
assert pl[0] == tmp_path
|
|
||||||
assert pl[1] == somepath
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("maybe_type", ["not passed", "None", '"string"'])
|
@pytest.mark.parametrize("maybe_type", ["not passed", "None", '"string"'])
|
||||||
def test_addini(self, pytester: Pytester, maybe_type: str) -> None:
|
def test_addini(self, pytester: Pytester, maybe_type: str) -> None:
|
||||||
|
|
|
@ -62,28 +62,22 @@ class TestConftestValueAccessGlobal:
|
||||||
def test_basic_init(self, basedir: Path) -> None:
|
def test_basic_init(self, basedir: Path) -> None:
|
||||||
conftest = PytestPluginManager()
|
conftest = PytestPluginManager()
|
||||||
p = basedir / "adir"
|
p = basedir / "adir"
|
||||||
assert (
|
conftest._loadconftestmodules(p, importmode="prepend", rootpath=basedir)
|
||||||
conftest._rget_with_confmod("a", p, importmode="prepend", rootpath=basedir)[
|
assert conftest._rget_with_confmod("a", p)[1] == 1
|
||||||
1
|
|
||||||
]
|
|
||||||
== 1
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_immediate_initialiation_and_incremental_are_the_same(
|
def test_immediate_initialiation_and_incremental_are_the_same(
|
||||||
self, basedir: Path
|
self, basedir: Path
|
||||||
) -> None:
|
) -> None:
|
||||||
conftest = PytestPluginManager()
|
conftest = PytestPluginManager()
|
||||||
assert not len(conftest._dirpath2confmods)
|
assert not len(conftest._dirpath2confmods)
|
||||||
conftest._getconftestmodules(
|
conftest._loadconftestmodules(basedir, importmode="prepend", rootpath=basedir)
|
||||||
basedir, importmode="prepend", rootpath=Path(basedir)
|
|
||||||
)
|
|
||||||
snap1 = len(conftest._dirpath2confmods)
|
snap1 = len(conftest._dirpath2confmods)
|
||||||
assert snap1 == 1
|
assert snap1 == 1
|
||||||
conftest._getconftestmodules(
|
conftest._loadconftestmodules(
|
||||||
basedir / "adir", importmode="prepend", rootpath=basedir
|
basedir / "adir", importmode="prepend", rootpath=basedir
|
||||||
)
|
)
|
||||||
assert len(conftest._dirpath2confmods) == snap1 + 1
|
assert len(conftest._dirpath2confmods) == snap1 + 1
|
||||||
conftest._getconftestmodules(
|
conftest._loadconftestmodules(
|
||||||
basedir / "b", importmode="prepend", rootpath=basedir
|
basedir / "b", importmode="prepend", rootpath=basedir
|
||||||
)
|
)
|
||||||
assert len(conftest._dirpath2confmods) == snap1 + 2
|
assert len(conftest._dirpath2confmods) == snap1 + 2
|
||||||
|
@ -91,33 +85,23 @@ class TestConftestValueAccessGlobal:
|
||||||
def test_value_access_not_existing(self, basedir: Path) -> None:
|
def test_value_access_not_existing(self, basedir: Path) -> None:
|
||||||
conftest = ConftestWithSetinitial(basedir)
|
conftest = ConftestWithSetinitial(basedir)
|
||||||
with pytest.raises(KeyError):
|
with pytest.raises(KeyError):
|
||||||
conftest._rget_with_confmod(
|
conftest._rget_with_confmod("a", basedir)
|
||||||
"a", basedir, importmode="prepend", rootpath=Path(basedir)
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_value_access_by_path(self, basedir: Path) -> None:
|
def test_value_access_by_path(self, basedir: Path) -> None:
|
||||||
conftest = ConftestWithSetinitial(basedir)
|
conftest = ConftestWithSetinitial(basedir)
|
||||||
adir = basedir / "adir"
|
adir = basedir / "adir"
|
||||||
assert (
|
conftest._loadconftestmodules(adir, importmode="prepend", rootpath=basedir)
|
||||||
conftest._rget_with_confmod(
|
assert conftest._rget_with_confmod("a", adir)[1] == 1
|
||||||
"a", adir, importmode="prepend", rootpath=basedir
|
conftest._loadconftestmodules(
|
||||||
)[1]
|
adir / "b", importmode="prepend", rootpath=basedir
|
||||||
== 1
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
conftest._rget_with_confmod(
|
|
||||||
"a", adir / "b", importmode="prepend", rootpath=basedir
|
|
||||||
)[1]
|
|
||||||
== 1.5
|
|
||||||
)
|
)
|
||||||
|
assert conftest._rget_with_confmod("a", adir / "b")[1] == 1.5
|
||||||
|
|
||||||
def test_value_access_with_confmod(self, basedir: Path) -> None:
|
def test_value_access_with_confmod(self, basedir: Path) -> None:
|
||||||
startdir = basedir / "adir" / "b"
|
startdir = basedir / "adir" / "b"
|
||||||
startdir.joinpath("xx").mkdir()
|
startdir.joinpath("xx").mkdir()
|
||||||
conftest = ConftestWithSetinitial(startdir)
|
conftest = ConftestWithSetinitial(startdir)
|
||||||
mod, value = conftest._rget_with_confmod(
|
mod, value = conftest._rget_with_confmod("a", startdir)
|
||||||
"a", startdir, importmode="prepend", rootpath=Path(basedir)
|
|
||||||
)
|
|
||||||
assert value == 1.5
|
assert value == 1.5
|
||||||
assert mod.__file__ is not None
|
assert mod.__file__ is not None
|
||||||
path = Path(mod.__file__)
|
path = Path(mod.__file__)
|
||||||
|
@ -143,9 +127,7 @@ def test_doubledash_considered(pytester: Pytester) -> None:
|
||||||
conf.joinpath("conftest.py").touch()
|
conf.joinpath("conftest.py").touch()
|
||||||
conftest = PytestPluginManager()
|
conftest = PytestPluginManager()
|
||||||
conftest_setinitial(conftest, [conf.name, conf.name])
|
conftest_setinitial(conftest, [conf.name, conf.name])
|
||||||
values = conftest._getconftestmodules(
|
values = conftest._getconftestmodules(conf)
|
||||||
conf, importmode="prepend", rootpath=pytester.path
|
|
||||||
)
|
|
||||||
assert len(values) == 1
|
assert len(values) == 1
|
||||||
|
|
||||||
|
|
||||||
|
@ -192,26 +174,22 @@ def test_conftestcutdir(pytester: Pytester) -> None:
|
||||||
p = pytester.mkdir("x")
|
p = pytester.mkdir("x")
|
||||||
conftest = PytestPluginManager()
|
conftest = PytestPluginManager()
|
||||||
conftest_setinitial(conftest, [pytester.path], confcutdir=p)
|
conftest_setinitial(conftest, [pytester.path], confcutdir=p)
|
||||||
values = conftest._getconftestmodules(
|
conftest._loadconftestmodules(p, importmode="prepend", rootpath=pytester.path)
|
||||||
p, importmode="prepend", rootpath=pytester.path
|
values = conftest._getconftestmodules(p)
|
||||||
)
|
|
||||||
assert len(values) == 0
|
assert len(values) == 0
|
||||||
values = conftest._getconftestmodules(
|
conftest._loadconftestmodules(
|
||||||
conf.parent, importmode="prepend", rootpath=pytester.path
|
conf.parent, importmode="prepend", rootpath=pytester.path
|
||||||
)
|
)
|
||||||
|
values = conftest._getconftestmodules(conf.parent)
|
||||||
assert len(values) == 0
|
assert len(values) == 0
|
||||||
assert not conftest.has_plugin(str(conf))
|
assert not conftest.has_plugin(str(conf))
|
||||||
# but we can still import a conftest directly
|
# but we can still import a conftest directly
|
||||||
conftest._importconftest(conf, importmode="prepend", rootpath=pytester.path)
|
conftest._importconftest(conf, importmode="prepend", rootpath=pytester.path)
|
||||||
values = conftest._getconftestmodules(
|
values = conftest._getconftestmodules(conf.parent)
|
||||||
conf.parent, importmode="prepend", rootpath=pytester.path
|
|
||||||
)
|
|
||||||
assert values[0].__file__ is not None
|
assert values[0].__file__ is not None
|
||||||
assert values[0].__file__.startswith(str(conf))
|
assert values[0].__file__.startswith(str(conf))
|
||||||
# and all sub paths get updated properly
|
# and all sub paths get updated properly
|
||||||
values = conftest._getconftestmodules(
|
values = conftest._getconftestmodules(p)
|
||||||
p, importmode="prepend", rootpath=pytester.path
|
|
||||||
)
|
|
||||||
assert len(values) == 1
|
assert len(values) == 1
|
||||||
assert values[0].__file__ is not None
|
assert values[0].__file__ is not None
|
||||||
assert values[0].__file__.startswith(str(conf))
|
assert values[0].__file__.startswith(str(conf))
|
||||||
|
@ -221,9 +199,7 @@ def test_conftestcutdir_inplace_considered(pytester: Pytester) -> None:
|
||||||
conf = pytester.makeconftest("")
|
conf = pytester.makeconftest("")
|
||||||
conftest = PytestPluginManager()
|
conftest = PytestPluginManager()
|
||||||
conftest_setinitial(conftest, [conf.parent], confcutdir=conf.parent)
|
conftest_setinitial(conftest, [conf.parent], confcutdir=conf.parent)
|
||||||
values = conftest._getconftestmodules(
|
values = conftest._getconftestmodules(conf.parent)
|
||||||
conf.parent, importmode="prepend", rootpath=pytester.path
|
|
||||||
)
|
|
||||||
assert len(values) == 1
|
assert len(values) == 1
|
||||||
assert values[0].__file__ is not None
|
assert values[0].__file__ is not None
|
||||||
assert values[0].__file__.startswith(str(conf))
|
assert values[0].__file__.startswith(str(conf))
|
||||||
|
@ -433,10 +409,8 @@ def test_conftest_import_order(pytester: Pytester, monkeypatch: MonkeyPatch) ->
|
||||||
conftest = PytestPluginManager()
|
conftest = PytestPluginManager()
|
||||||
conftest._confcutdir = pytester.path
|
conftest._confcutdir = pytester.path
|
||||||
monkeypatch.setattr(conftest, "_importconftest", impct)
|
monkeypatch.setattr(conftest, "_importconftest", impct)
|
||||||
mods = cast(
|
conftest._loadconftestmodules(sub, importmode="prepend", rootpath=pytester.path)
|
||||||
List[Path],
|
mods = cast(List[Path], conftest._getconftestmodules(sub))
|
||||||
conftest._getconftestmodules(sub, importmode="prepend", rootpath=pytester.path),
|
|
||||||
)
|
|
||||||
expected = [ct1, ct2]
|
expected = [ct1, ct2]
|
||||||
assert mods == expected
|
assert mods == expected
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue