Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider pyproject.toml files for config if no other config files were found #11962

Merged
merged 3 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/11962.improvement.rst
Original file line number Diff line number Diff line change
@@ -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``.
13 changes: 9 additions & 4 deletions doc/en/reference/customize.rst
Original file line number Diff line number Diff line change
Expand Up @@ -177,13 +177,20 @@ 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``).
Comment on lines +180 to +181
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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``).
Finally, a ``pyproject.toml`` file will be considered the ``configfile`` if no other match was found,
even if it does not contain a ``[tool.pytest.ini_options]`` table (this was added in ``8.1``). It will thus define the root dir.

It was unclear for me at first what the purpose of a pyproject.toml without any pytest related config inside was.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Used a slightly different approach, moving this to its own line as it is independent from pyproject.toml itself.


The files are considered in the order above. Options from multiple ``configfiles`` candidates
are never merged - the first match wins.

The configuration file also determines the value of the ``rootpath``.

The :class:`Config <pytest.Config>` object (accessible via hooks or through the :fixture:`pytestconfig` fixture)
will subsequently carry these attributes:

- :attr:`config.rootpath <pytest.Config.rootpath>`: the determined root directory, guaranteed to exist.
- :attr:`config.rootpath <pytest.Config.rootpath>`: the determined root directory, guaranteed to exist. It is used as
a reference directory for constructing test addresses ("nodeids") and can be used also by plugins for storing
per-testrun information.

- :attr:`config.inipath <pytest.Config.inipath>`: the determined ``configfile``, may be ``None``
(it is named ``inipath`` for historical reasons).
Expand All @@ -193,9 +200,7 @@ will subsequently carry these attributes:
versions of the older ``config.rootdir`` and ``config.inifile``, which have type
``py.path.local``, and still exist for backward compatibility.

The ``rootdir`` is used as a reference directory for constructing test
addresses ("nodeids") and can be used also by plugins for storing
per-testrun information.


Example:

Expand Down
5 changes: 5 additions & 0 deletions src/_pytest/config/findpaths.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,15 +101,20 @@
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, {}

Check warning on line 117 in src/_pytest/config/findpaths.py

View check run for this annotation

Codecov / codecov/patch

src/_pytest/config/findpaths.py#L117

Added line #L117 was not covered by tests
return None, None, {}


Expand Down
32 changes: 31 additions & 1 deletion testing/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
"""A pytest.ini always take precedence over a pyproject.toml file."""
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(
Expand Down