config: fix the paths considered for initial conftest discovery

Fixes #11104.

See the issue for a description of the problem.

Now, we use the same logic for initial conftest paths as we do for
deciding the initial args, which was the idea behind checking
`namespace.file_or_dir` and `testpaths` previously.

This fixes the issue of `testpaths` being considered for initial
conftests even when it's not used for the args.

(Another issue in faeb16146b was that the
`testpaths` were not glob-expanded, this is also fixed.)
This commit is contained in:
Ran Benita 2023-06-20 20:59:22 +03:00
parent d97d44a97a
commit 14890329dc
4 changed files with 53 additions and 29 deletions

View File

@ -0,0 +1,3 @@
Fixed a regression in pytest 7.3.2 which caused to :confval:`testpaths` to be considered for loading initial conftests,
even when it was not utilized (e.g. when explicit paths were given on the command line).
Now the ``testpaths`` are only considered when they are in use.

View File

@ -527,9 +527,12 @@ class PytestPluginManager(PluginManager):
# #
def _set_initial_conftests( def _set_initial_conftests(
self, self,
namespace: argparse.Namespace, args: Sequence[Union[str, Path]],
pyargs: bool,
noconftest: bool,
rootpath: Path, rootpath: Path,
testpaths_ini: Sequence[str], confcutdir: Optional[Path],
importmode: Union[ImportMode, str],
) -> None: ) -> None:
"""Load initial conftest files given a preparsed "namespace". """Load initial conftest files given a preparsed "namespace".
@ -539,17 +542,12 @@ class PytestPluginManager(PluginManager):
common options will not confuse our logic here. common options will not confuse our logic here.
""" """
current = Path.cwd() current = Path.cwd()
self._confcutdir = ( self._confcutdir = absolutepath(current / confcutdir) if confcutdir else None
absolutepath(current / namespace.confcutdir) self._noconftest = noconftest
if namespace.confcutdir self._using_pyargs = pyargs
else None
)
self._noconftest = namespace.noconftest
self._using_pyargs = namespace.pyargs
testpaths = namespace.file_or_dir + testpaths_ini
foundanchor = False foundanchor = False
for testpath in testpaths: for intitial_path in args:
path = str(testpath) path = str(intitial_path)
# remove node-id syntax # remove node-id syntax
i = path.find("::") i = path.find("::")
if i != -1: if i != -1:
@ -563,10 +561,10 @@ class PytestPluginManager(PluginManager):
except OSError: # pragma: no cover except OSError: # pragma: no cover
anchor_exists = False anchor_exists = False
if anchor_exists: if anchor_exists:
self._try_load_conftest(anchor, namespace.importmode, rootpath) self._try_load_conftest(anchor, importmode, rootpath)
foundanchor = True foundanchor = True
if not foundanchor: if not foundanchor:
self._try_load_conftest(current, namespace.importmode, rootpath) self._try_load_conftest(current, importmode, rootpath)
def _is_in_confcutdir(self, path: Path) -> bool: def _is_in_confcutdir(self, path: Path) -> bool:
"""Whether a path is within the confcutdir. """Whether a path is within the confcutdir.
@ -1140,10 +1138,25 @@ class Config:
@hookimpl(trylast=True) @hookimpl(trylast=True)
def pytest_load_initial_conftests(self, early_config: "Config") -> None: def pytest_load_initial_conftests(self, early_config: "Config") -> None:
self.pluginmanager._set_initial_conftests( # We haven't fully parsed the command line arguments yet, so
early_config.known_args_namespace, # early_config.args it not set yet. But we need it for
# discovering the initial conftests. So "pre-run" the logic here.
# It will be done for real in `parse()`.
args, args_source = early_config._decide_args(
args=early_config.known_args_namespace.file_or_dir,
pyargs=early_config.known_args_namespace.pyargs,
testpaths=early_config.getini("testpaths"),
invocation_dir=early_config.invocation_params.dir,
rootpath=early_config.rootpath, rootpath=early_config.rootpath,
testpaths_ini=self.getini("testpaths"), warn=False,
)
self.pluginmanager._set_initial_conftests(
args=args,
pyargs=early_config.known_args_namespace.pyargs,
noconftest=early_config.known_args_namespace.noconftest,
rootpath=early_config.rootpath,
confcutdir=early_config.known_args_namespace.confcutdir,
importmode=early_config.known_args_namespace.importmode,
) )
def _initini(self, args: Sequence[str]) -> None: def _initini(self, args: Sequence[str]) -> None:

View File

@ -1264,11 +1264,18 @@ def test_initial_conftests_with_testpaths(pytester: Pytester) -> None:
testpaths = some_path testpaths = some_path
""" """
) )
# No command line args - falls back to testpaths.
result = pytester.runpytest() result = pytester.runpytest()
assert result.ret == ExitCode.INTERNAL_ERROR
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
"INTERNALERROR* Exception: pytest_sessionstart hook successfully run" "INTERNALERROR* Exception: pytest_sessionstart hook successfully run"
) )
# No fallback.
result = pytester.runpytest(".")
assert result.ret == ExitCode.NO_TESTS_COLLECTED
def test_large_option_breaks_initial_conftests(pytester: Pytester) -> None: def test_large_option_breaks_initial_conftests(pytester: Pytester) -> None:
"""Long option values do not break initial conftests handling (#10169).""" """Long option values do not break initial conftests handling (#10169)."""

View File

@ -1,4 +1,3 @@
import argparse
import os import os
import textwrap import textwrap
from pathlib import Path from pathlib import Path
@ -7,6 +6,8 @@ from typing import Dict
from typing import Generator from typing import Generator
from typing import List from typing import List
from typing import Optional from typing import Optional
from typing import Sequence
from typing import Union
import pytest import pytest
from _pytest.config import ExitCode from _pytest.config import ExitCode
@ -24,18 +25,18 @@ def ConftestWithSetinitial(path) -> PytestPluginManager:
def conftest_setinitial( def conftest_setinitial(
conftest: PytestPluginManager, args, confcutdir: Optional["os.PathLike[str]"] = None conftest: PytestPluginManager,
args: Sequence[Union[str, Path]],
confcutdir: Optional[Path] = None,
) -> None: ) -> None:
class Namespace: conftest._set_initial_conftests(
def __init__(self) -> None: args=args,
self.file_or_dir = args pyargs=False,
self.confcutdir = os.fspath(confcutdir) if confcutdir is not None else None noconftest=False,
self.noconftest = False rootpath=Path(args[0]),
self.pyargs = False confcutdir=confcutdir,
self.importmode = "prepend" importmode="prepend",
)
namespace = cast(argparse.Namespace, Namespace())
conftest._set_initial_conftests(namespace, rootpath=Path(args[0]), testpaths_ini=[])
@pytest.mark.usefixtures("_sys_snapshot") @pytest.mark.usefixtures("_sys_snapshot")