From b241c0b479bb409e496db5751a97dac0bd3ad4d9 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 11 May 2023 09:30:43 -0300 Subject: [PATCH 1/4] Fix defaults for tmp_path_retention_count and tmp_path_retention_policy in docs --- doc/en/reference/reference.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index 8b42bf12965..18b2da95376 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -1744,7 +1744,7 @@ passed multiple times. The expected format is ``name=value``. For example:: [pytest] tmp_path_retention_count = 3 - Default: 3 + Default: ``3`` .. confval:: tmp_path_retention_policy @@ -1763,7 +1763,7 @@ passed multiple times. The expected format is ``name=value``. For example:: [pytest] tmp_path_retention_policy = "all" - Default: all + Default: ``all`` .. confval:: usefixtures From faeb16146b811488ebbcbd17ef6f9102314065b2 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 11 May 2023 09:22:17 -0300 Subject: [PATCH 2/4] Consider testpaths for initial conftests The 'testpaths' option is meant to be identical to execute pytest passing the 'testpaths' directories explicitly. Fix #10987 --- changelog/10987.bugfix.rst | 1 + doc/en/reference/reference.rst | 16 ++++++++++++---- src/_pytest/config/__init__.py | 26 ++++++++++++++++++++++---- testing/test_collection.py | 23 +++++++++++++++++++++++ testing/test_conftest.py | 2 +- 5 files changed, 59 insertions(+), 9 deletions(-) create mode 100644 changelog/10987.bugfix.rst diff --git a/changelog/10987.bugfix.rst b/changelog/10987.bugfix.rst new file mode 100644 index 00000000000..2aafff5f512 --- /dev/null +++ b/changelog/10987.bugfix.rst @@ -0,0 +1 @@ +:confval:`testpaths` is now honored to load root ``conftests``. diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index 18b2da95376..4bda48386a1 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -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 ` 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. @@ -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 diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 720f3953153..25cf75e9806 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -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". @@ -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) @@ -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: # 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) foundanchor = True if not foundanchor: @@ -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: diff --git a/testing/test_collection.py b/testing/test_collection.py index d907244d551..f78ae7beafb 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -1247,6 +1247,29 @@ def test_collect_pyargs_with_testpaths( 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: """Collect a direct symlink works even if it does not match python_files (#4325).""" real = pytester.makepyfile( diff --git a/testing/test_conftest.py b/testing/test_conftest.py index d2bf860c6fe..d6abca5368f 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -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") From 2d57d5c32f6cce9058c24317dfce4c3de22ad2a3 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 12 May 2023 09:27:24 -0300 Subject: [PATCH 3/4] Do not break on very long command-line options `_set_initial_conftests` could break on some systems if a very long option was passed, because the `Path.exists()` call raises an `OSError` instead of returning `False`. Fix #10169 --- changelog/10169.bugfix.rst | 1 + src/_pytest/config/__init__.py | 18 ++++++------------ testing/test_collection.py | 23 +++++++++++++++++++++-- 3 files changed, 28 insertions(+), 14 deletions(-) create mode 100644 changelog/10169.bugfix.rst diff --git a/changelog/10169.bugfix.rst b/changelog/10169.bugfix.rst new file mode 100644 index 00000000000..cbf3516a93d --- /dev/null +++ b/changelog/10169.bugfix.rst @@ -0,0 +1 @@ +Fix bug where very long option names could cause pytest to break with ``OSError: [Errno 36] File name too long`` on some systems. diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 25cf75e9806..74905ff4c8e 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -556,19 +556,13 @@ def _set_initial_conftests( path = path[:i] anchor = absolutepath(current / path) - # 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: + # Ensure we do not break if what appears to be an anchor + # is in fact a very long option (#10169). + try: anchor_exists = anchor.exists() - if anchor_exists: # We found some file object. + except OSError: # pragma: no cover + anchor_exists = False + if anchor_exists: self._try_load_conftest(anchor, namespace.importmode, rootpath) foundanchor = True if not foundanchor: diff --git a/testing/test_collection.py b/testing/test_collection.py index f78ae7beafb..bbcb358b6ff 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -1254,7 +1254,7 @@ def test_initial_conftests_with_testpaths(pytester: Pytester) -> None: textwrap.dedent( """ def pytest_sessionstart(session): - raise Exception("pytest_sessionstart hook is successfully run") + raise Exception("pytest_sessionstart hook successfully run") """ ) ) @@ -1266,10 +1266,29 @@ def pytest_sessionstart(session): ) result = pytester.runpytest() result.stdout.fnmatch_lines( - "INTERNALERROR* Exception: pytest_sessionstart hook is successfully run" + "INTERNALERROR* Exception: pytest_sessionstart hook successfully run" ) +def test_large_option_breaks_initial_conftests(pytester: Pytester) -> None: + """Long option values do not break initial conftests handling (#10169).""" + option_value = "x" * 1024 * 1000 + pytester.makeconftest( + """ + def pytest_addoption(parser): + parser.addoption("--xx", default=None) + """ + ) + pytester.makepyfile( + f""" + def test_foo(request): + assert request.config.getoption("xx") == {option_value!r} + """ + ) + result = pytester.runpytest(f"--xx={option_value}") + assert result.ret == 0 + + 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( From 4cc05e7bee158ba12698880e8bb83d5040aa277d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 12 May 2023 09:34:44 -0300 Subject: [PATCH 4/4] Fix trailing whitespace in .github/workflows/stale.yml --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 384d4bc63fd..eba0eefd72e 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -2,7 +2,7 @@ name: close needs-information issues on: schedule: - cron: "30 1 * * *" - workflow_dispatch: + workflow_dispatch: jobs: close-issues: