Merge branch 'main' into Improvement-move-add-pseudo-funcarg-to-metafunc-parametrize

This commit is contained in:
Sadra Barikbin 2023-08-09 19:21:45 +03:30
commit 2853331cb4
16 changed files with 171 additions and 125 deletions

View File

@ -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]

View File

@ -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.

View File

@ -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`` statements
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.

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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.

View File

@ -1236,8 +1236,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.
:: ::
@ -1564,7 +1565,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
] ]
@ -1730,7 +1731,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,

View File

@ -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)

View File

@ -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

View File

@ -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:

View File

@ -162,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)],
), ),
) )
@ -198,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):
@ -1580,6 +1582,66 @@ class TestMetafuncFunctional:
assert test_1_fixture_x is test_2_fixture_x 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(
"""
import pytest
@pytest.fixture(scope="module")
def fixture0():
pass
@pytest.fixture(scope="module")
def fixture1(fixture0):
pass
@pytest.mark.parametrize("fixture1", [0], indirect=True)
def test_0(fixture1):
pass
@pytest.fixture(scope="module")
def fixture():
pass
@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("-v")
assert result.ret == 0
result.stdout.fnmatch_lines(
[
"*test_0*",
"*test_1*",
"*test_2*",
"*test_3*",
]
)
class TestMetafuncFunctionalAuto: class TestMetafuncFunctionalAuto:
"""Tests related to automatically find out the correct scope for """Tests related to automatically find out the correct scope for

View File

@ -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:

View File

@ -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