mirror of https://github.com/pytest-dev/pytest.git
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