Merge branch 'main' into Improvement-catch-duplicate-values-when-determining-param-indices-in-metafunc-parametrize

This commit is contained in:
Sadra Barikbin 2023-08-10 12:51:14 +03:30
commit da499bbd23
16 changed files with 219 additions and 162 deletions

View File

@ -29,7 +29,7 @@ repos:
language: python
files: \.py$
- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
rev: 6.1.0
hooks:
- id: flake8
language_version: python3
@ -42,7 +42,7 @@ repos:
- id: reorder-python-imports
args: ['--application-directories=.:src', --py38-plus]
- repo: https://github.com/asottile/pyupgrade
rev: v3.9.0
rev: v3.10.1
hooks:
- id: pyupgrade
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
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:
.. code-block:: python
from contextlib import nullcontext as does_not_raise
from contextlib import nullcontext
import pytest
@ -671,16 +674,17 @@ For example:
@pytest.mark.parametrize(
"example_input,expectation",
[
(3, does_not_raise()),
(2, does_not_raise()),
(1, does_not_raise()),
(3, nullcontext(2)),
(2, nullcontext(3)),
(1, nullcontext(6)),
(0, pytest.raises(ZeroDivisionError)),
],
)
def test_division(example_input, expectation):
"""Test how much I know division."""
with expectation:
assert (6 / example_input) is not None
with expectation as e:
assert (6 / example_input) == e
In the example above, the first three test cases should run unexceptionally,
while the fourth should raise ``ZeroDivisionError``.
In the example above, the first three test cases should run without any
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
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
assert a % 2 == 0, "value was odd, should be even"
then no assertion introspection takes places at all and the message
will be simply shown in the traceback.
it is printed alongside the assertion introspection in the traceback.
See :ref:`assert-details` for more information on assertion introspection.

View File

@ -82,6 +82,8 @@ pytest.exit
pytest.main
~~~~~~~~~~~
**Tutorial**: :ref:`pytest.main-usage`
.. autofunction:: pytest.main
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
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)
):
# Note: unlike dataclasses/attrs, namedtuples compare only the

View File

@ -581,26 +581,25 @@ class PytestPluginManager(PluginManager):
def _try_load_conftest(
self, anchor: Path, importmode: Union[str, ImportMode], rootpath: Path
) -> None:
self._getconftestmodules(anchor, importmode, rootpath)
self._loadconftestmodules(anchor, importmode, rootpath)
# let's also consider test* subdirs
if anchor.is_dir():
for x in anchor.glob("test*"):
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
) -> Sequence[types.ModuleType]:
) -> None:
if self._noconftest:
return []
return
directory = self._get_directory(path)
# Optimization: avoid repeated searches in the same directory.
# Assumes always called with same importmode and rootpath.
existing_clist = self._dirpath2confmods.get(directory)
if existing_clist is not None:
return existing_clist
if directory in self._dirpath2confmods:
return
# XXX these days we may rather want to use config.rootpath
# and allow users to opt into looking into the rootdir parent
@ -613,16 +612,17 @@ class PytestPluginManager(PluginManager):
mod = self._importconftest(conftestpath, importmode, rootpath)
clist.append(mod)
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(
self,
name: str,
path: Path,
importmode: Union[str, ImportMode],
rootpath: Path,
) -> Tuple[types.ModuleType, Any]:
modules = self._getconftestmodules(path, importmode, rootpath=rootpath)
modules = self._getconftestmodules(path)
for mod in reversed(modules):
try:
return mod, getattr(mod, name)
@ -1562,13 +1562,9 @@ class Config:
else:
return self._getini_unknown_type(name, type, value)
def _getconftest_pathlist(
self, name: str, path: Path, rootpath: Path
) -> Optional[List[Path]]:
def _getconftest_pathlist(self, name: str, path: Path) -> Optional[List[Path]]:
try:
mod, relroots = self.pluginmanager._rget_with_confmod(
name, path, self.getoption("importmode"), rootpath
)
mod, relroots = self.pluginmanager._rget_with_confmod(name, path)
except KeyError:
return 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.
_Key = Tuple[object, ...]
@dataclasses.dataclass(frozen=True)
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
the specified scope."""
assert scope is not Scope.Function
@ -169,24 +175,28 @@ def get_parametrized_fixture_keys(item: nodes.Item, scope: Scope) -> Iterator[_K
pass
else:
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
# 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:
continue
item_cls = None
if scope is Scope.Session:
key: _Key = (argname, param_index)
scoped_item_path = None
elif scope is Scope.Package:
key = (argname, param_index, item.path)
scoped_item_path = item.path
elif scope is Scope.Module:
key = (argname, param_index, item.path)
scoped_item_path = item.path
elif scope is Scope.Class:
scoped_item_path = item.path
item_cls = item.cls # type: ignore[attr-defined]
key = (argname, param_index, item.path, item_cls)
else:
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.
@ -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]:
argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[_Key, None]]] = {}
items_by_argkey: Dict[Scope, Dict[_Key, Deque[nodes.Item]]] = {}
argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]] = {}
items_by_argkey: Dict[Scope, Dict[FixtureArgKey, Deque[nodes.Item]]] = {}
for scope in HIGH_SCOPES:
d: Dict[nodes.Item, Dict[_Key, None]] = {}
d: Dict[nodes.Item, Dict[FixtureArgKey, None]] = {}
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
for item in items:
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(
item: nodes.Item,
argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[_Key, None]]],
items_by_argkey: Dict[Scope, Dict[_Key, "Deque[nodes.Item]"]],
argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]],
items_by_argkey: Dict[Scope, Dict[FixtureArgKey, "Deque[nodes.Item]"]],
) -> None:
for scope in HIGH_SCOPES:
for key in argkeys_cache[scope].get(item, []):
@ -227,13 +237,13 @@ def fix_cache_order(
def reorder_items_atscope(
items: Dict[nodes.Item, None],
argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[_Key, None]]],
items_by_argkey: Dict[Scope, Dict[_Key, "Deque[nodes.Item]"]],
argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]],
items_by_argkey: Dict[Scope, Dict[FixtureArgKey, "Deque[nodes.Item]"]],
scope: Scope,
) -> Dict[nodes.Item, None]:
if scope is Scope.Function or len(items) < 3:
return items
ignore: Set[Optional[_Key]] = set()
ignore: Set[Optional[FixtureArgKey]] = set()
items_deque = deque(items)
items_done: Dict[nodes.Item, None] = {}
scoped_items_by_argkey = items_by_argkey[scope]
@ -394,7 +404,7 @@ class FixtureRequest:
node: Optional[Union[nodes.Item, nodes.Collector]] = self._pyfuncitem
elif scope is Scope.Package:
# 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]
else:
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]:
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 []
excludeopt = config.getoption("ignore")
@ -387,7 +387,7 @@ def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[boo
return True
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 []
excludeglobopt = config.getoption("ignore_glob")
@ -551,11 +551,16 @@ class Session(nodes.FSCollector):
pm = self.config.pluginmanager
# Check if we have the common case of running
# 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,
self.config.getoption("importmode"),
rootpath=self.config.rootpath,
)
my_conftestmodules = pm._getconftestmodules(path)
remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
if remove_mods:
# One or more conftests are not in use at this fspath.

View File

@ -41,7 +41,6 @@ from _pytest._code.code import Traceback
from _pytest._io import TerminalWriter
from _pytest._io.saferepr import saferepr
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_real_func
from _pytest.compat import getimfunc
@ -498,8 +497,11 @@ class PyCollector(PyobjMixin, nodes.Collector):
if not metafunc._calls:
yield Function.from_parent(self, name=name, fixtureinfo=fixtureinfo)
else:
# Dynamic direct parametrization may have shadowed some fixtures,
# so make sure we update what the function really needs.
# Direct parametrizations taking place in module/class-specific
# `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()
for callspec in metafunc._calls:
@ -1283,8 +1285,9 @@ class Metafunc:
during the collection phase. If you need to setup expensive resources
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
previous parametrizations, e.g.
Can be called multiple times per test function (but only on different
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
# node related to the scope.
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_)
if node is None:
# If used class scope and there is no class, use module-level
@ -1402,7 +1406,7 @@ class Metafunc:
elif scope_ is Scope.Package:
node = collector.session
else:
assert_never(scope_) # type: ignore[arg-type]
assert False, f"Unhandled missing scope: {scope}"
if node is None:
name2pseudofixturedef = None
else:
@ -1410,9 +1414,9 @@ class Metafunc:
name2pseudofixturedef = node.stash.setdefault(
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:
if arg_values_types[argname] == "params":
if arg_directness[argname] == "indirect":
continue
if name2pseudofixturedef is not None and argname in name2pseudofixturedef:
fixturedef = name2pseudofixturedef[argname]
@ -1517,28 +1521,30 @@ class Metafunc:
return list(itertools.islice(ids, num_ids))
def _resolve_arg_value_types(
def _resolve_args_directness(
self,
argnames: Sequence[str],
indirect: Union[bool, Sequence[str]],
) -> Dict[str, "Literal['params', 'funcargs']"]:
"""Resolve if each parametrized argument must be considered a
parameter to a fixture or a "funcarg" to the function, based on the
``indirect`` parameter of the parametrized() call.
) -> Dict[str, Literal["indirect", "direct"]]:
"""Resolve if each parametrized argument must be considered an indirect
parameter to a fixture of the same name, or a direct parameter to the
parametrized function, based on the ``indirect`` parameter of the
parametrized() call.
:param List[str] argnames: List of argument names passed to ``parametrize()``.
:param indirect: Same as the ``indirect`` parameter of ``parametrize()``.
:rtype: Dict[str, str]
A dict mapping each arg name to either:
* "params" if the argname should be the parameter of a fixture of the same name.
* "funcargs" if the argname should be a parameter to the parametrized test function.
:param argnames:
List of argument names passed to ``parametrize()``.
:param indirect:
Same as the ``indirect`` parameter of ``parametrize()``.
:returns
A dict mapping each arg name to either "indirect" or "direct".
"""
arg_directness: Dict[str, Literal["indirect", "direct"]]
if isinstance(indirect, bool):
valtypes: Dict[str, Literal["params", "funcargs"]] = dict.fromkeys(
argnames, "params" if indirect else "funcargs"
arg_directness = dict.fromkeys(
argnames, "indirect" if indirect else "direct"
)
elif isinstance(indirect, Sequence):
valtypes = dict.fromkeys(argnames, "funcargs")
arg_directness = dict.fromkeys(argnames, "direct")
for arg in indirect:
if arg not in argnames:
fail(
@ -1547,7 +1553,7 @@ class Metafunc:
),
pytrace=False,
)
valtypes[arg] = "params"
arg_directness[arg] = "indirect"
else:
fail(
"In {func}: expected Sequence or boolean for indirect, got {type}".format(
@ -1555,7 +1561,7 @@ class Metafunc:
),
pytrace=False,
)
return valtypes
return arg_directness
def _validate_if_using_arg_names(
self,
@ -1612,7 +1618,7 @@ def _find_parametrized_scope(
if all_arguments_are_fixtures:
fixturedefs = arg2fixturedefs or {}
used_scopes = [
fixturedef[0]._scope
fixturedef[-1]._scope
for name, fixturedef in fixturedefs.items()
if name in argnames
]
@ -1778,7 +1784,7 @@ class Function(PyobjMixin, nodes.Item):
:param config:
The pytest Config object.
: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.
:param callobj:
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")
s = x.read_text("ascii")
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
django==4.2.3
django==4.2.4
pytest-asyncio==0.21.1
pytest-bdd==6.1.1
pytest-cov==4.1.0

View File

@ -2103,9 +2103,7 @@ class TestAutouseManagement:
reprec = pytester.inline_run("-v", "-s", "--confcutdir", pytester.path)
reprec.assertoutcome(passed=8)
config = reprec.getcalls("pytest_unconfigure")[0].config
values = config.pluginmanager._getconftestmodules(
p, importmode="prepend", rootpath=pytester.path
)[0].values
values = config.pluginmanager._getconftestmodules(p)[0].values
assert values == ["fin_a1", "fin_a2", "fin_b1", "fin_b2"] * 2
def test_scope_ordering(self, pytester: Pytester) -> None:

View File

@ -23,6 +23,7 @@ from _pytest.compat import getfuncargnames
from _pytest.compat import NOTSET
from _pytest.outcomes import fail
from _pytest.pytester import Pytester
from _pytest.python import Function
from _pytest.python import IdMaker
from _pytest.scope import Scope
@ -161,6 +162,7 @@ class TestMetafunc:
module_fix=[DummyFixtureDef(Scope.Module)],
class_fix=[DummyFixtureDef(Scope.Class)],
func_fix=[DummyFixtureDef(Scope.Function)],
mixed_fix=[DummyFixtureDef(Scope.Module), DummyFixtureDef(Scope.Class)],
),
)
@ -197,6 +199,7 @@ class TestMetafunc:
)
== Scope.Module
)
assert find_scope(["mixed_fix"], indirect=True) == Scope.Class
def test_parametrize_and_id(self) -> None:
def func(x, y):
@ -1588,29 +1591,94 @@ class TestMetafuncFunctional:
def test_parametrize_module_level_test_with_class_scope(
self, pytester: Pytester
) -> 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(
"""
import pytest
@pytest.fixture
def item(request):
return request._pyfuncitem
@pytest.fixture(scope="module")
def fixture0():
pass
fixturedef = None
@pytest.fixture(scope="module")
def fixture1(fixture0):
pass
@pytest.mark.parametrize("x", [0, 1], scope="class")
def test_1(item, x):
global fixturedef
fixturedef = item._fixtureinfo.name2fixturedefs['x'][-1]
@pytest.mark.parametrize("fixture1", [0], indirect=True)
def test_0(fixture1):
pass
@pytest.mark.parametrize("x", [1, 2], scope="module")
def test_2(item, x):
global fixturedef
assert fixturedef == item._fixtureinfo.name2fixturedefs['x'][-1]
"""
@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()
result = pytester.runpytest("-v")
assert result.ret == 0
result.stdout.fnmatch_lines(
[
"*test_0*",
"*test_1*",
"*test_2*",
"*test_3*",
]
)
class TestMetafuncFunctionalAuto:

View File

@ -642,18 +642,11 @@ class TestConfigAPI:
p = tmp_path.joinpath("conftest.py")
p.write_text(f"mylist = {['.', str(somepath)]}", encoding="utf-8")
config = pytester.parseconfigure(p)
assert (
config._getconftest_pathlist("notexist", path=tmp_path, rootpath=tmp_path)
is None
)
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
assert config._getconftest_pathlist("notexist", path=tmp_path) is None
assert config._getconftest_pathlist("mylist", path=tmp_path) == [
tmp_path,
somepath,
]
@pytest.mark.parametrize("maybe_type", ["not passed", "None", '"string"'])
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:
conftest = PytestPluginManager()
p = basedir / "adir"
assert (
conftest._rget_with_confmod("a", p, importmode="prepend", rootpath=basedir)[
1
]
== 1
)
conftest._loadconftestmodules(p, importmode="prepend", rootpath=basedir)
assert conftest._rget_with_confmod("a", p)[1] == 1
def test_immediate_initialiation_and_incremental_are_the_same(
self, basedir: Path
) -> None:
conftest = PytestPluginManager()
assert not len(conftest._dirpath2confmods)
conftest._getconftestmodules(
basedir, importmode="prepend", rootpath=Path(basedir)
)
conftest._loadconftestmodules(basedir, importmode="prepend", rootpath=basedir)
snap1 = len(conftest._dirpath2confmods)
assert snap1 == 1
conftest._getconftestmodules(
conftest._loadconftestmodules(
basedir / "adir", importmode="prepend", rootpath=basedir
)
assert len(conftest._dirpath2confmods) == snap1 + 1
conftest._getconftestmodules(
conftest._loadconftestmodules(
basedir / "b", importmode="prepend", rootpath=basedir
)
assert len(conftest._dirpath2confmods) == snap1 + 2
@ -91,33 +85,23 @@ class TestConftestValueAccessGlobal:
def test_value_access_not_existing(self, basedir: Path) -> None:
conftest = ConftestWithSetinitial(basedir)
with pytest.raises(KeyError):
conftest._rget_with_confmod(
"a", basedir, importmode="prepend", rootpath=Path(basedir)
)
conftest._rget_with_confmod("a", basedir)
def test_value_access_by_path(self, basedir: Path) -> None:
conftest = ConftestWithSetinitial(basedir)
adir = basedir / "adir"
assert (
conftest._rget_with_confmod(
"a", adir, importmode="prepend", rootpath=basedir
)[1]
== 1
)
assert (
conftest._rget_with_confmod(
"a", adir / "b", importmode="prepend", rootpath=basedir
)[1]
== 1.5
conftest._loadconftestmodules(adir, importmode="prepend", rootpath=basedir)
assert conftest._rget_with_confmod("a", adir)[1] == 1
conftest._loadconftestmodules(
adir / "b", importmode="prepend", rootpath=basedir
)
assert conftest._rget_with_confmod("a", adir / "b")[1] == 1.5
def test_value_access_with_confmod(self, basedir: Path) -> None:
startdir = basedir / "adir" / "b"
startdir.joinpath("xx").mkdir()
conftest = ConftestWithSetinitial(startdir)
mod, value = conftest._rget_with_confmod(
"a", startdir, importmode="prepend", rootpath=Path(basedir)
)
mod, value = conftest._rget_with_confmod("a", startdir)
assert value == 1.5
assert mod.__file__ is not None
path = Path(mod.__file__)
@ -143,9 +127,7 @@ def test_doubledash_considered(pytester: Pytester) -> None:
conf.joinpath("conftest.py").touch()
conftest = PytestPluginManager()
conftest_setinitial(conftest, [conf.name, conf.name])
values = conftest._getconftestmodules(
conf, importmode="prepend", rootpath=pytester.path
)
values = conftest._getconftestmodules(conf)
assert len(values) == 1
@ -192,26 +174,22 @@ def test_conftestcutdir(pytester: Pytester) -> None:
p = pytester.mkdir("x")
conftest = PytestPluginManager()
conftest_setinitial(conftest, [pytester.path], confcutdir=p)
values = conftest._getconftestmodules(
p, importmode="prepend", rootpath=pytester.path
)
conftest._loadconftestmodules(p, importmode="prepend", rootpath=pytester.path)
values = conftest._getconftestmodules(p)
assert len(values) == 0
values = conftest._getconftestmodules(
conftest._loadconftestmodules(
conf.parent, importmode="prepend", rootpath=pytester.path
)
values = conftest._getconftestmodules(conf.parent)
assert len(values) == 0
assert not conftest.has_plugin(str(conf))
# but we can still import a conftest directly
conftest._importconftest(conf, importmode="prepend", rootpath=pytester.path)
values = conftest._getconftestmodules(
conf.parent, importmode="prepend", rootpath=pytester.path
)
values = conftest._getconftestmodules(conf.parent)
assert values[0].__file__ is not None
assert values[0].__file__.startswith(str(conf))
# and all sub paths get updated properly
values = conftest._getconftestmodules(
p, importmode="prepend", rootpath=pytester.path
)
values = conftest._getconftestmodules(p)
assert len(values) == 1
assert values[0].__file__ is not None
assert values[0].__file__.startswith(str(conf))
@ -221,9 +199,7 @@ def test_conftestcutdir_inplace_considered(pytester: Pytester) -> None:
conf = pytester.makeconftest("")
conftest = PytestPluginManager()
conftest_setinitial(conftest, [conf.parent], confcutdir=conf.parent)
values = conftest._getconftestmodules(
conf.parent, importmode="prepend", rootpath=pytester.path
)
values = conftest._getconftestmodules(conf.parent)
assert len(values) == 1
assert values[0].__file__ is not None
assert values[0].__file__.startswith(str(conf))
@ -433,10 +409,8 @@ def test_conftest_import_order(pytester: Pytester, monkeypatch: MonkeyPatch) ->
conftest = PytestPluginManager()
conftest._confcutdir = pytester.path
monkeypatch.setattr(conftest, "_importconftest", impct)
mods = cast(
List[Path],
conftest._getconftestmodules(sub, importmode="prepend", rootpath=pytester.path),
)
conftest._loadconftestmodules(sub, importmode="prepend", rootpath=pytester.path)
mods = cast(List[Path], conftest._getconftestmodules(sub))
expected = [ct1, ct2]
assert mods == expected