diff --git a/changelog/11962.improvement.rst b/changelog/11962.improvement.rst new file mode 100644 index 00000000000..453b99d33d7 --- /dev/null +++ b/changelog/11962.improvement.rst @@ -0,0 +1 @@ +In case no other suitable candidates for configuration file are found, a ``pyproject.toml`` (even without a ``[tool.pytest.ini_options]`` table) will be considered as the configuration file and define the ``rootdir``. diff --git a/doc/en/reference/customize.rst b/doc/en/reference/customize.rst index 24c0ed21752..6c339b8892d 100644 --- a/doc/en/reference/customize.rst +++ b/doc/en/reference/customize.rst @@ -177,6 +177,9 @@ Files will only be matched for configuration if: * ``tox.ini``: contains a ``[pytest]`` section. * ``setup.cfg``: contains a ``[tool:pytest]`` section. +Finally, a ``pyproject.toml`` file will be considered the ``configfile`` if no other match was found, in this case +even if it does not contain a ``[tool.pytest.ini_options]`` table (this was added in ``8.1``). + The files are considered in the order above. Options from multiple ``configfiles`` candidates are never merged - the first match wins. diff --git a/src/_pytest/config/findpaths.py b/src/_pytest/config/findpaths.py index 96f3536514e..9909376de0f 100644 --- a/src/_pytest/config/findpaths.py +++ b/src/_pytest/config/findpaths.py @@ -101,15 +101,20 @@ def locate_config( args = [x for x in args if not str(x).startswith("-")] if not args: args = [invocation_dir] + found_pyproject_toml: Optional[Path] = None for arg in args: argpath = absolutepath(arg) for base in (argpath, *argpath.parents): for config_name in config_names: p = base / config_name if p.is_file(): + if p.name == "pyproject.toml" and found_pyproject_toml is None: + found_pyproject_toml = p ini_config = load_config_dict_from_file(p) if ini_config is not None: return base, p, ini_config + if found_pyproject_toml is not None: + return found_pyproject_toml.parent, found_pyproject_toml, {} return None, None, {} diff --git a/testing/test_config.py b/testing/test_config.py index ead693cf48b..8ff03261c59 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -135,15 +135,45 @@ def test_ini_names(self, pytester: Pytester, name, section) -> None: assert config.getini("minversion") == "3.36" def test_pyproject_toml(self, pytester: Pytester) -> None: - pytester.makepyprojecttoml( + pyproject_toml = pytester.makepyprojecttoml( """ [tool.pytest.ini_options] minversion = "1.0" """ ) config = pytester.parseconfig() + assert config.inipath == pyproject_toml assert config.getini("minversion") == "1.0" + def test_empty_pyproject_toml(self, pytester: Pytester) -> None: + """An empty pyproject.toml is considered as config if no other option is found.""" + pyproject_toml = pytester.makepyprojecttoml("") + config = pytester.parseconfig() + assert config.inipath == pyproject_toml + + def test_empty_pyproject_toml_found_many(self, pytester: Pytester) -> None: + """ + In case we find multiple pyproject.toml files in our search, without a [tool.pytest.ini_options] + table and without finding other candidates, the closest to where we started wins. + """ + pytester.makefile( + ".toml", + **{ + "pyproject": "", + "foo/pyproject": "", + "foo/bar/pyproject": "", + }, + ) + config = pytester.parseconfig(pytester.path / "foo/bar") + assert config.inipath == pytester.path / "foo/bar/pyproject.toml" + + def test_pytest_ini_trumps_pyproject_toml(self, pytester: Pytester) -> None: + """An empty pyproject.toml is considered as config if no other option is found.""" + pytester.makepyprojecttoml("[tool.pytest.ini_options]") + pytest_ini = pytester.makefile(".ini", pytest="") + config = pytester.parseconfig() + assert config.inipath == pytest_ini + def test_toxini_before_lower_pytestini(self, pytester: Pytester) -> None: sub = pytester.mkdir("sub") sub.joinpath("tox.ini").write_text(