Consider testpaths for initial conftests

The 'testpaths' option is meant to be identical to execute
pytest passing the 'testpaths' directories explicitly.

Fix #10987
This commit is contained in:
Bruno Oliveira 2023-05-11 09:22:17 -03:00
parent b241c0b479
commit faeb16146b
5 changed files with 59 additions and 9 deletions

View File

@ -0,0 +1 @@
:confval:`testpaths` is now honored to load root ``conftests``.

View File

@ -1713,13 +1713,12 @@ passed multiple times. The expected format is ``name=value``. For example::
.. confval:: testpaths .. confval:: testpaths
Sets list of directories that should be searched for tests when Sets list of directories that should be searched for tests when
no specific directories, files or test ids are given in the command line when no specific directories, files or test ids are given in the command line when
executing pytest from the :ref:`rootdir <rootdir>` directory. executing pytest from the :ref:`rootdir <rootdir>` directory.
File system paths may use shell-style wildcards, including the recursive File system paths may use shell-style wildcards, including the recursive
``**`` pattern. ``**`` pattern.
Useful when all project tests are in a known location to speed up Useful when all project tests are in a known location to speed up
test collection and to avoid picking up undesired tests by accident. test collection and to avoid picking up undesired tests by accident.
@ -1728,8 +1727,17 @@ passed multiple times. The expected format is ``name=value``. For example::
[pytest] [pytest]
testpaths = testing doc testpaths = testing doc
This tells pytest to only look for tests in ``testing`` and ``doc`` This configuration means that executing:
directories when executing from the root directory.
.. code-block:: console
pytest
has the same practical effects as executing:
.. code-block:: console
pytest testing doc
.. confval:: tmp_path_retention_count .. confval:: tmp_path_retention_count

View File

@ -526,7 +526,10 @@ class PytestPluginManager(PluginManager):
# Internal API for local conftest plugin handling. # Internal API for local conftest plugin handling.
# #
def _set_initial_conftests( def _set_initial_conftests(
self, namespace: argparse.Namespace, rootpath: Path self,
namespace: argparse.Namespace,
rootpath: Path,
testpaths_ini: Sequence[str],
) -> None: ) -> None:
"""Load initial conftest files given a preparsed "namespace". """Load initial conftest files given a preparsed "namespace".
@ -543,7 +546,7 @@ class PytestPluginManager(PluginManager):
) )
self._noconftest = namespace.noconftest self._noconftest = namespace.noconftest
self._using_pyargs = namespace.pyargs self._using_pyargs = namespace.pyargs
testpaths = namespace.file_or_dir testpaths = namespace.file_or_dir + testpaths_ini
foundanchor = False foundanchor = False
for testpath in testpaths: for testpath in testpaths:
path = str(testpath) path = str(testpath)
@ -552,7 +555,20 @@ class PytestPluginManager(PluginManager):
if i != -1: if i != -1:
path = path[:i] path = path[:i]
anchor = absolutepath(current / path) anchor = absolutepath(current / path)
if anchor.exists(): # we found some file object
# On Python 3.7 on Windows, anchor.exists() might raise
# if the anchor contains glob characters (for example "*//tests"), specially
# in the case of the 'testpaths' ini option.
# Using an explicit version check to remove this code later once
# Python 3.7 is dropped.
if sys.version_info[:2] == (3, 7):
try:
anchor_exists = anchor.exists()
except OSError: # pragma: no cover
anchor_exists = False
else:
anchor_exists = anchor.exists()
if anchor_exists: # We found some file object.
self._try_load_conftest(anchor, namespace.importmode, rootpath) self._try_load_conftest(anchor, namespace.importmode, rootpath)
foundanchor = True foundanchor = True
if not foundanchor: if not foundanchor:
@ -1131,7 +1147,9 @@ 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( self.pluginmanager._set_initial_conftests(
early_config.known_args_namespace, rootpath=early_config.rootpath early_config.known_args_namespace,
rootpath=early_config.rootpath,
testpaths_ini=self.getini("testpaths"),
) )
def _initini(self, args: Sequence[str]) -> None: def _initini(self, args: Sequence[str]) -> None:

View File

@ -1247,6 +1247,29 @@ def test_collect_pyargs_with_testpaths(
result.stdout.fnmatch_lines(["*1 passed in*"]) result.stdout.fnmatch_lines(["*1 passed in*"])
def test_initial_conftests_with_testpaths(pytester: Pytester) -> None:
"""The testpaths ini option should load conftests in those paths as 'initial' (#10987)."""
p = pytester.mkdir("some_path")
p.joinpath("conftest.py").write_text(
textwrap.dedent(
"""
def pytest_sessionstart(session):
raise Exception("pytest_sessionstart hook is successfully run")
"""
)
)
pytester.makeini(
"""
[pytest]
testpaths = some_path
"""
)
result = pytester.runpytest()
result.stdout.fnmatch_lines(
"INTERNALERROR* Exception: pytest_sessionstart hook is successfully run"
)
def test_collect_symlink_file_arg(pytester: Pytester) -> None: def test_collect_symlink_file_arg(pytester: Pytester) -> None:
"""Collect a direct symlink works even if it does not match python_files (#4325).""" """Collect a direct symlink works even if it does not match python_files (#4325)."""
real = pytester.makepyfile( real = pytester.makepyfile(

View File

@ -35,7 +35,7 @@ def conftest_setinitial(
self.importmode = "prepend" self.importmode = "prepend"
namespace = cast(argparse.Namespace, Namespace()) namespace = cast(argparse.Namespace, Namespace())
conftest._set_initial_conftests(namespace, rootpath=Path(args[0])) conftest._set_initial_conftests(namespace, rootpath=Path(args[0]), testpaths_ini=[])
@pytest.mark.usefixtures("_sys_snapshot") @pytest.mark.usefixtures("_sys_snapshot")