Skip to content

Commit

Permalink
config: fix the paths considered for initial conftest discovery
Browse files Browse the repository at this point in the history
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 faeb161 was that the
`testpaths` were not glob-expanded, this is also fixed.)
  • Loading branch information
bluetech committed Jun 21, 2023
1 parent d97d44a commit 1489032
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 28 deletions.
3 changes: 3 additions & 0 deletions changelog/11104.bugfix.rst
@@ -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.
45 changes: 29 additions & 16 deletions src/_pytest/config/__init__.py
Expand Up @@ -527,9 +527,12 @@ def pytest_configure(self, config: "Config") -> None:
#
def _set_initial_conftests(
self,
namespace: argparse.Namespace,
args: Sequence[Union[str, Path]],
pyargs: bool,
noconftest: bool,
rootpath: Path,
testpaths_ini: Sequence[str],
confcutdir: Optional[Path],
importmode: Union[ImportMode, str],
) -> None:
"""Load initial conftest files given a preparsed "namespace".
Expand All @@ -539,17 +542,12 @@ def _set_initial_conftests(
common options will not confuse our logic here.
"""
current = Path.cwd()
self._confcutdir = (
absolutepath(current / namespace.confcutdir)
if namespace.confcutdir
else None
)
self._noconftest = namespace.noconftest
self._using_pyargs = namespace.pyargs
testpaths = namespace.file_or_dir + testpaths_ini
self._confcutdir = absolutepath(current / confcutdir) if confcutdir else None
self._noconftest = noconftest
self._using_pyargs = pyargs
foundanchor = False
for testpath in testpaths:
path = str(testpath)
for intitial_path in args:
path = str(intitial_path)
# remove node-id syntax
i = path.find("::")
if i != -1:
Expand All @@ -563,10 +561,10 @@ def _set_initial_conftests(
except OSError: # pragma: no cover
anchor_exists = False
if anchor_exists:
self._try_load_conftest(anchor, namespace.importmode, rootpath)
self._try_load_conftest(anchor, importmode, rootpath)
foundanchor = True
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:
"""Whether a path is within the confcutdir.
Expand Down Expand Up @@ -1140,10 +1138,25 @@ def _processopt(self, opt: "Argument") -> None:

@hookimpl(trylast=True)
def pytest_load_initial_conftests(self, early_config: "Config") -> None:
# We haven't fully parsed the command line arguments yet, so
# 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,
warn=False,
)
self.pluginmanager._set_initial_conftests(
early_config.known_args_namespace,
args=args,
pyargs=early_config.known_args_namespace.pyargs,
noconftest=early_config.known_args_namespace.noconftest,
rootpath=early_config.rootpath,
testpaths_ini=self.getini("testpaths"),
confcutdir=early_config.known_args_namespace.confcutdir,
importmode=early_config.known_args_namespace.importmode,
)

def _initini(self, args: Sequence[str]) -> None:
Expand Down
7 changes: 7 additions & 0 deletions testing/test_collection.py
Expand Up @@ -1264,11 +1264,18 @@ def pytest_sessionstart(session):
testpaths = some_path
"""
)

# No command line args - falls back to testpaths.
result = pytester.runpytest()
assert result.ret == ExitCode.INTERNAL_ERROR
result.stdout.fnmatch_lines(
"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:
"""Long option values do not break initial conftests handling (#10169)."""
Expand Down
25 changes: 13 additions & 12 deletions testing/test_conftest.py
@@ -1,4 +1,3 @@
import argparse
import os
import textwrap
from pathlib import Path
Expand All @@ -7,6 +6,8 @@
from typing import Generator
from typing import List
from typing import Optional
from typing import Sequence
from typing import Union

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


def conftest_setinitial(
conftest: PytestPluginManager, args, confcutdir: Optional["os.PathLike[str]"] = None
conftest: PytestPluginManager,
args: Sequence[Union[str, Path]],
confcutdir: Optional[Path] = None,
) -> None:
class Namespace:
def __init__(self) -> None:
self.file_or_dir = args
self.confcutdir = os.fspath(confcutdir) if confcutdir is not None else None
self.noconftest = False
self.pyargs = False
self.importmode = "prepend"

namespace = cast(argparse.Namespace, Namespace())
conftest._set_initial_conftests(namespace, rootpath=Path(args[0]), testpaths_ini=[])
conftest._set_initial_conftests(
args=args,
pyargs=False,
noconftest=False,
rootpath=Path(args[0]),
confcutdir=confcutdir,
importmode="prepend",
)


@pytest.mark.usefixtures("_sys_snapshot")
Expand Down

0 comments on commit 1489032

Please sign in to comment.