Skip to content

Commit

Permalink
Consider testpaths for initial conftests
Browse files Browse the repository at this point in the history
The 'testpaths' option is meant to be identical to execute
pytest passing the 'testpaths' directories explicitly.

Fix pytest-dev#10987
  • Loading branch information
nicoddemus committed May 11, 2023
1 parent c8bcc32 commit 2c38d8d
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 9 deletions.
1 change: 1 addition & 0 deletions changelog/10987.bugfix.rst
@@ -0,0 +1 @@
:confval:`testpaths` is now honored to load root ``conftests``.
16 changes: 12 additions & 4 deletions doc/en/reference/reference.rst
Expand Up @@ -1713,13 +1713,12 @@ passed multiple times. The expected format is ``name=value``. For example::
.. confval:: testpaths



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
executing pytest from the :ref:`rootdir <rootdir>` directory.
File system paths may use shell-style wildcards, including the recursive
``**`` pattern.

Useful when all project tests are in a known location to speed up
test collection and to avoid picking up undesired tests by accident.

Expand All @@ -1728,8 +1727,17 @@ passed multiple times. The expected format is ``name=value``. For example::
[pytest]
testpaths = testing doc
This tells pytest to only look for tests in ``testing`` and ``doc``
directories when executing from the root directory.
This configuration means that executing:

.. code-block:: console
pytest
has the same practical effects as executing:

.. code-block:: console
pytest testing doc
.. confval:: tmp_path_retention_count
Expand Down
26 changes: 22 additions & 4 deletions src/_pytest/config/__init__.py
Expand Up @@ -526,7 +526,10 @@ def pytest_configure(self, config: "Config") -> None:
# Internal API for local conftest plugin handling.
#
def _set_initial_conftests(
self, namespace: argparse.Namespace, rootpath: Path
self,
namespace: argparse.Namespace,
rootpath: Path,
testpaths_ini: Sequence[str],
) -> None:
"""Load initial conftest files given a preparsed "namespace".
Expand All @@ -543,7 +546,7 @@ def _set_initial_conftests(
)
self._noconftest = namespace.noconftest
self._using_pyargs = namespace.pyargs
testpaths = namespace.file_or_dir
testpaths = namespace.file_or_dir + testpaths_ini
foundanchor = False
for testpath in testpaths:
path = str(testpath)
Expand All @@ -552,7 +555,20 @@ def _set_initial_conftests(
if i != -1:
path = path[:i]
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:
anchor_exists = False
else:
anchor_exists = anchor.exists()
if anchor_exists: # We found some file object.
self._try_load_conftest(anchor, namespace.importmode, rootpath)
foundanchor = True
if not foundanchor:
Expand Down Expand Up @@ -1131,7 +1147,9 @@ def _processopt(self, opt: "Argument") -> None:
@hookimpl(trylast=True)
def pytest_load_initial_conftests(self, early_config: "Config") -> None:
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:
Expand Down
22 changes: 22 additions & 0 deletions testing/test_collection.py
Expand Up @@ -1247,6 +1247,28 @@ def test_collect_pyargs_with_testpaths(
result.stdout.fnmatch_lines(["*1 passed in*"])


def test_initial_conftests_with_testpaths(pytester: Pytester) -> None:
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:
"""Collect a direct symlink works even if it does not match python_files (#4325)."""
real = pytester.makepyfile(
Expand Down
2 changes: 1 addition & 1 deletion testing/test_conftest.py
Expand Up @@ -35,7 +35,7 @@ def __init__(self) -> None:
self.importmode = "prepend"

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")
Expand Down

0 comments on commit 2c38d8d

Please sign in to comment.