Merge pull request #11125 from bluetech/initial-conftests-testpaths
config: fix the paths considered for initial conftest discovery
This commit is contained in:
		
						commit
						faa1f9d2ad
					
				| 
						 | 
					@ -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.
 | 
				
			||||||
| 
						 | 
					@ -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:
 | 
				
			||||||
| 
						 | 
					@ -1223,6 +1236,49 @@ class Config:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return args
 | 
					        return args
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _decide_args(
 | 
				
			||||||
 | 
					        self,
 | 
				
			||||||
 | 
					        *,
 | 
				
			||||||
 | 
					        args: List[str],
 | 
				
			||||||
 | 
					        pyargs: List[str],
 | 
				
			||||||
 | 
					        testpaths: List[str],
 | 
				
			||||||
 | 
					        invocation_dir: Path,
 | 
				
			||||||
 | 
					        rootpath: Path,
 | 
				
			||||||
 | 
					        warn: bool,
 | 
				
			||||||
 | 
					    ) -> Tuple[List[str], ArgsSource]:
 | 
				
			||||||
 | 
					        """Decide the args (initial paths/nodeids) to use given the relevant inputs.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param warn: Whether can issue warnings.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if args:
 | 
				
			||||||
 | 
					            source = Config.ArgsSource.ARGS
 | 
				
			||||||
 | 
					            result = args
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            if invocation_dir == rootpath:
 | 
				
			||||||
 | 
					                source = Config.ArgsSource.TESTPATHS
 | 
				
			||||||
 | 
					                if pyargs:
 | 
				
			||||||
 | 
					                    result = testpaths
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    result = []
 | 
				
			||||||
 | 
					                    for path in testpaths:
 | 
				
			||||||
 | 
					                        result.extend(sorted(glob.iglob(path, recursive=True)))
 | 
				
			||||||
 | 
					                    if testpaths and not result:
 | 
				
			||||||
 | 
					                        if warn:
 | 
				
			||||||
 | 
					                            warning_text = (
 | 
				
			||||||
 | 
					                                "No files were found in testpaths; "
 | 
				
			||||||
 | 
					                                "consider removing or adjusting your testpaths configuration. "
 | 
				
			||||||
 | 
					                                "Searching recursively from the current directory instead."
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                            self.issue_config_time_warning(
 | 
				
			||||||
 | 
					                                PytestConfigWarning(warning_text), stacklevel=3
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                result = []
 | 
				
			||||||
 | 
					            if not result:
 | 
				
			||||||
 | 
					                source = Config.ArgsSource.INCOVATION_DIR
 | 
				
			||||||
 | 
					                result = [str(invocation_dir)]
 | 
				
			||||||
 | 
					        return result, source
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _preparse(self, args: List[str], addopts: bool = True) -> None:
 | 
					    def _preparse(self, args: List[str], addopts: bool = True) -> None:
 | 
				
			||||||
        if addopts:
 | 
					        if addopts:
 | 
				
			||||||
            env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
 | 
					            env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
 | 
				
			||||||
| 
						 | 
					@ -1371,34 +1427,17 @@ class Config:
 | 
				
			||||||
        self.hook.pytest_cmdline_preparse(config=self, args=args)
 | 
					        self.hook.pytest_cmdline_preparse(config=self, args=args)
 | 
				
			||||||
        self._parser.after_preparse = True  # type: ignore
 | 
					        self._parser.after_preparse = True  # type: ignore
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            source = Config.ArgsSource.ARGS
 | 
					 | 
				
			||||||
            args = self._parser.parse_setoption(
 | 
					            args = self._parser.parse_setoption(
 | 
				
			||||||
                args, self.option, namespace=self.option
 | 
					                args, self.option, namespace=self.option
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            if not args:
 | 
					            self.args, self.args_source = self._decide_args(
 | 
				
			||||||
                if self.invocation_params.dir == self.rootpath:
 | 
					                args=args,
 | 
				
			||||||
                    source = Config.ArgsSource.TESTPATHS
 | 
					                pyargs=self.known_args_namespace.pyargs,
 | 
				
			||||||
                    testpaths: List[str] = self.getini("testpaths")
 | 
					                testpaths=self.getini("testpaths"),
 | 
				
			||||||
                    if self.known_args_namespace.pyargs:
 | 
					                invocation_dir=self.invocation_params.dir,
 | 
				
			||||||
                        args = testpaths
 | 
					                rootpath=self.rootpath,
 | 
				
			||||||
                    else:
 | 
					                warn=True,
 | 
				
			||||||
                        args = []
 | 
					 | 
				
			||||||
                        for path in testpaths:
 | 
					 | 
				
			||||||
                            args.extend(sorted(glob.iglob(path, recursive=True)))
 | 
					 | 
				
			||||||
                        if testpaths and not args:
 | 
					 | 
				
			||||||
                            warning_text = (
 | 
					 | 
				
			||||||
                                "No files were found in testpaths; "
 | 
					 | 
				
			||||||
                                "consider removing or adjusting your testpaths configuration. "
 | 
					 | 
				
			||||||
                                "Searching recursively from the current directory instead."
 | 
					 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
                            self.issue_config_time_warning(
 | 
					 | 
				
			||||||
                                PytestConfigWarning(warning_text), stacklevel=3
 | 
					 | 
				
			||||||
                            )
 | 
					 | 
				
			||||||
                if not args:
 | 
					 | 
				
			||||||
                    source = Config.ArgsSource.INCOVATION_DIR
 | 
					 | 
				
			||||||
                    args = [str(self.invocation_params.dir)]
 | 
					 | 
				
			||||||
            self.args = args
 | 
					 | 
				
			||||||
            self.args_source = source
 | 
					 | 
				
			||||||
        except PrintHelp:
 | 
					        except PrintHelp:
 | 
				
			||||||
            pass
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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)."""
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue