diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4bed87035..be6d08221 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -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] diff --git a/changelog/11277.bugfix.rst b/changelog/11277.bugfix.rst new file mode 100644 index 000000000..43370561e --- /dev/null +++ b/changelog/11277.bugfix.rst @@ -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. diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index f771e5da4..4ea6f6e65 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -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`` statement’s +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. diff --git a/doc/en/how-to/assert.rst b/doc/en/how-to/assert.rst index 1b10c1313..d99a1ce5c 100644 --- a/doc/en/how-to/assert.rst +++ b/doc/en/how-to/assert.rst @@ -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. diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index ea008110e..a6d7cfdd4 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -82,6 +82,8 @@ pytest.exit pytest.main ~~~~~~~~~~~ +**Tutorial**: :ref:`pytest.main-usage` + .. autofunction:: pytest.main pytest.param diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index fc5dfdbd5..268f714ba 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -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 diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 2b6f250f3..8dbaf7c70 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -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 diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 01dae5741..be0dce17c 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -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) diff --git a/src/_pytest/main.py b/src/_pytest/main.py index 4c3c9aed4..fd3836736 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -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. diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 3e4089d3c..be76237b2 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -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, diff --git a/testing/_py/test_local.py b/testing/_py/test_local.py index 895066a9f..91b14aa2e 100644 --- a/testing/_py/test_local.py +++ b/testing/_py/test_local.py @@ -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) diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index cf580b7cc..d46300fe0 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -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 diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index 191689d1c..7c0282772 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -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: diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 25d32ee4d..c2cc88d32 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -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: diff --git a/testing/test_config.py b/testing/test_config.py index 43561000c..04161f238 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -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: diff --git a/testing/test_conftest.py b/testing/test_conftest.py index 427831507..cfc2d577b 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -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