Skip to content

Commit faa1f9d

Browse files
authored
Merge pull request #11125 from bluetech/initial-conftests-testpaths
config: fix the paths considered for initial conftest discovery
2 parents 797b924 + 1489032 commit faa1f9d

File tree

4 files changed

+103
-53
lines changed

4 files changed

+103
-53
lines changed

changelog/11104.bugfix.rst

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

src/_pytest/config/__init__.py

+80-41
Original file line numberDiff line numberDiff line change
@@ -527,9 +527,12 @@ def pytest_configure(self, config: "Config") -> None:
527527
#
528528
def _set_initial_conftests(
529529
self,
530-
namespace: argparse.Namespace,
530+
args: Sequence[Union[str, Path]],
531+
pyargs: bool,
532+
noconftest: bool,
531533
rootpath: Path,
532-
testpaths_ini: Sequence[str],
534+
confcutdir: Optional[Path],
535+
importmode: Union[ImportMode, str],
533536
) -> None:
534537
"""Load initial conftest files given a preparsed "namespace".
535538
@@ -539,17 +542,12 @@ def _set_initial_conftests(
539542
common options will not confuse our logic here.
540543
"""
541544
current = Path.cwd()
542-
self._confcutdir = (
543-
absolutepath(current / namespace.confcutdir)
544-
if namespace.confcutdir
545-
else None
546-
)
547-
self._noconftest = namespace.noconftest
548-
self._using_pyargs = namespace.pyargs
549-
testpaths = namespace.file_or_dir + testpaths_ini
545+
self._confcutdir = absolutepath(current / confcutdir) if confcutdir else None
546+
self._noconftest = noconftest
547+
self._using_pyargs = pyargs
550548
foundanchor = False
551-
for testpath in testpaths:
552-
path = str(testpath)
549+
for intitial_path in args:
550+
path = str(intitial_path)
553551
# remove node-id syntax
554552
i = path.find("::")
555553
if i != -1:
@@ -563,10 +561,10 @@ def _set_initial_conftests(
563561
except OSError: # pragma: no cover
564562
anchor_exists = False
565563
if anchor_exists:
566-
self._try_load_conftest(anchor, namespace.importmode, rootpath)
564+
self._try_load_conftest(anchor, importmode, rootpath)
567565
foundanchor = True
568566
if not foundanchor:
569-
self._try_load_conftest(current, namespace.importmode, rootpath)
567+
self._try_load_conftest(current, importmode, rootpath)
570568

571569
def _is_in_confcutdir(self, path: Path) -> bool:
572570
"""Whether a path is within the confcutdir.
@@ -1140,10 +1138,25 @@ def _processopt(self, opt: "Argument") -> None:
11401138

11411139
@hookimpl(trylast=True)
11421140
def pytest_load_initial_conftests(self, early_config: "Config") -> None:
1141+
# We haven't fully parsed the command line arguments yet, so
1142+
# early_config.args it not set yet. But we need it for
1143+
# discovering the initial conftests. So "pre-run" the logic here.
1144+
# It will be done for real in `parse()`.
1145+
args, args_source = early_config._decide_args(
1146+
args=early_config.known_args_namespace.file_or_dir,
1147+
pyargs=early_config.known_args_namespace.pyargs,
1148+
testpaths=early_config.getini("testpaths"),
1149+
invocation_dir=early_config.invocation_params.dir,
1150+
rootpath=early_config.rootpath,
1151+
warn=False,
1152+
)
11431153
self.pluginmanager._set_initial_conftests(
1144-
early_config.known_args_namespace,
1154+
args=args,
1155+
pyargs=early_config.known_args_namespace.pyargs,
1156+
noconftest=early_config.known_args_namespace.noconftest,
11451157
rootpath=early_config.rootpath,
1146-
testpaths_ini=self.getini("testpaths"),
1158+
confcutdir=early_config.known_args_namespace.confcutdir,
1159+
importmode=early_config.known_args_namespace.importmode,
11471160
)
11481161

11491162
def _initini(self, args: Sequence[str]) -> None:
@@ -1223,6 +1236,49 @@ def _validate_args(self, args: List[str], via: str) -> List[str]:
12231236

12241237
return args
12251238

1239+
def _decide_args(
1240+
self,
1241+
*,
1242+
args: List[str],
1243+
pyargs: List[str],
1244+
testpaths: List[str],
1245+
invocation_dir: Path,
1246+
rootpath: Path,
1247+
warn: bool,
1248+
) -> Tuple[List[str], ArgsSource]:
1249+
"""Decide the args (initial paths/nodeids) to use given the relevant inputs.
1250+
1251+
:param warn: Whether can issue warnings.
1252+
"""
1253+
if args:
1254+
source = Config.ArgsSource.ARGS
1255+
result = args
1256+
else:
1257+
if invocation_dir == rootpath:
1258+
source = Config.ArgsSource.TESTPATHS
1259+
if pyargs:
1260+
result = testpaths
1261+
else:
1262+
result = []
1263+
for path in testpaths:
1264+
result.extend(sorted(glob.iglob(path, recursive=True)))
1265+
if testpaths and not result:
1266+
if warn:
1267+
warning_text = (
1268+
"No files were found in testpaths; "
1269+
"consider removing or adjusting your testpaths configuration. "
1270+
"Searching recursively from the current directory instead."
1271+
)
1272+
self.issue_config_time_warning(
1273+
PytestConfigWarning(warning_text), stacklevel=3
1274+
)
1275+
else:
1276+
result = []
1277+
if not result:
1278+
source = Config.ArgsSource.INCOVATION_DIR
1279+
result = [str(invocation_dir)]
1280+
return result, source
1281+
12261282
def _preparse(self, args: List[str], addopts: bool = True) -> None:
12271283
if addopts:
12281284
env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
@@ -1371,34 +1427,17 @@ def parse(self, args: List[str], addopts: bool = True) -> None:
13711427
self.hook.pytest_cmdline_preparse(config=self, args=args)
13721428
self._parser.after_preparse = True # type: ignore
13731429
try:
1374-
source = Config.ArgsSource.ARGS
13751430
args = self._parser.parse_setoption(
13761431
args, self.option, namespace=self.option
13771432
)
1378-
if not args:
1379-
if self.invocation_params.dir == self.rootpath:
1380-
source = Config.ArgsSource.TESTPATHS
1381-
testpaths: List[str] = self.getini("testpaths")
1382-
if self.known_args_namespace.pyargs:
1383-
args = testpaths
1384-
else:
1385-
args = []
1386-
for path in testpaths:
1387-
args.extend(sorted(glob.iglob(path, recursive=True)))
1388-
if testpaths and not args:
1389-
warning_text = (
1390-
"No files were found in testpaths; "
1391-
"consider removing or adjusting your testpaths configuration. "
1392-
"Searching recursively from the current directory instead."
1393-
)
1394-
self.issue_config_time_warning(
1395-
PytestConfigWarning(warning_text), stacklevel=3
1396-
)
1397-
if not args:
1398-
source = Config.ArgsSource.INCOVATION_DIR
1399-
args = [str(self.invocation_params.dir)]
1400-
self.args = args
1401-
self.args_source = source
1433+
self.args, self.args_source = self._decide_args(
1434+
args=args,
1435+
pyargs=self.known_args_namespace.pyargs,
1436+
testpaths=self.getini("testpaths"),
1437+
invocation_dir=self.invocation_params.dir,
1438+
rootpath=self.rootpath,
1439+
warn=True,
1440+
)
14021441
except PrintHelp:
14031442
pass
14041443

testing/test_collection.py

+7
Original file line numberDiff line numberDiff line change
@@ -1264,11 +1264,18 @@ def pytest_sessionstart(session):
12641264
testpaths = some_path
12651265
"""
12661266
)
1267+
1268+
# No command line args - falls back to testpaths.
12671269
result = pytester.runpytest()
1270+
assert result.ret == ExitCode.INTERNAL_ERROR
12681271
result.stdout.fnmatch_lines(
12691272
"INTERNALERROR* Exception: pytest_sessionstart hook successfully run"
12701273
)
12711274

1275+
# No fallback.
1276+
result = pytester.runpytest(".")
1277+
assert result.ret == ExitCode.NO_TESTS_COLLECTED
1278+
12721279

12731280
def test_large_option_breaks_initial_conftests(pytester: Pytester) -> None:
12741281
"""Long option values do not break initial conftests handling (#10169)."""

testing/test_conftest.py

+13-12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import argparse
21
import os
32
import textwrap
43
from pathlib import Path
@@ -7,6 +6,8 @@
76
from typing import Generator
87
from typing import List
98
from typing import Optional
9+
from typing import Sequence
10+
from typing import Union
1011

1112
import pytest
1213
from _pytest.config import ExitCode
@@ -24,18 +25,18 @@ def ConftestWithSetinitial(path) -> PytestPluginManager:
2425

2526

2627
def conftest_setinitial(
27-
conftest: PytestPluginManager, args, confcutdir: Optional["os.PathLike[str]"] = None
28+
conftest: PytestPluginManager,
29+
args: Sequence[Union[str, Path]],
30+
confcutdir: Optional[Path] = None,
2831
) -> None:
29-
class Namespace:
30-
def __init__(self) -> None:
31-
self.file_or_dir = args
32-
self.confcutdir = os.fspath(confcutdir) if confcutdir is not None else None
33-
self.noconftest = False
34-
self.pyargs = False
35-
self.importmode = "prepend"
36-
37-
namespace = cast(argparse.Namespace, Namespace())
38-
conftest._set_initial_conftests(namespace, rootpath=Path(args[0]), testpaths_ini=[])
32+
conftest._set_initial_conftests(
33+
args=args,
34+
pyargs=False,
35+
noconftest=False,
36+
rootpath=Path(args[0]),
37+
confcutdir=confcutdir,
38+
importmode="prepend",
39+
)
3940

4041

4142
@pytest.mark.usefixtures("_sys_snapshot")

0 commit comments

Comments
 (0)