diff --git a/AUTHORS b/AUTHORS index 33ee644ea68..2765347567b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -135,6 +135,7 @@ David Peled David Szotten David Vierra Daw-Ran Liou +Debaditya Hait Debi Mishra Denis Cherednichenko Denis Kirisov diff --git a/changelog/14705.bugfix.rst b/changelog/14705.bugfix.rst new file mode 100644 index 00000000000..f1b33a3d3f6 --- /dev/null +++ b/changelog/14705.bugfix.rst @@ -0,0 +1,2 @@ +Custom TOML configuration files selected with :option:`-c` now read options from +the ``[pytest]`` table instead of ``[tool.pytest]``. diff --git a/doc/en/reference/customize.rst b/doc/en/reference/customize.rst index 7a4030d081a..d0200218c31 100644 --- a/doc/en/reference/customize.rst +++ b/doc/en/reference/customize.rst @@ -32,6 +32,9 @@ pytest.toml Alternatively, the hidden version ``.pytest.toml`` can be used. +A TOML configuration file with a custom name can be loaded using :option:`-c`; +like ``pytest.toml``, it uses the ``[pytest]`` table. + .. tab:: toml .. code-block:: toml diff --git a/src/_pytest/config/findpaths.py b/src/_pytest/config/findpaths.py index 2a4bed319a9..d14022cc81e 100644 --- a/src/_pytest/config/findpaths.py +++ b/src/_pytest/config/findpaths.py @@ -92,7 +92,7 @@ def load_config_dict_from_file( # '.toml' files are considered if they contain a [tool.pytest] table (toml mode) # or [tool.pytest.ini_options] table (ini mode) for pyproject.toml, - # or [pytest] table (toml mode) for pytest.toml/.pytest.toml. + # or [pytest] table (toml mode) for other TOML configuration files. elif filepath.suffix == ".toml": if sys.version_info >= (3, 11): import tomllib @@ -105,8 +105,8 @@ def load_config_dict_from_file( except tomllib.TOMLDecodeError as exc: raise UsageError(f"{filepath}: {exc}") from exc - # pytest.toml and .pytest.toml use [pytest] table directly. - if filepath.name in ("pytest.toml", ".pytest.toml"): + # TOML configuration files other than pyproject.toml use [pytest] directly. + if filepath.name != "pyproject.toml": if "pytest" in config: # TOML mode - preserve native TOML types. return { @@ -122,7 +122,7 @@ def load_config_dict_from_file( f"[pytest] table (found top-level options: " f"{', '.join(top_level_options)})" ) - # "pytest.toml" files are always the source of configuration, even if empty. + # These files are always the source of configuration, even if empty. return {} # pyproject.toml uses [tool.pytest] or [tool.pytest.ini_options]. diff --git a/testing/test_config.py b/testing/test_config.py index 9583c9131fa..9f9b8f614f4 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -766,8 +766,8 @@ def pytest_addoption(parser): pytester.makefile( ".toml", custom=""" - [tool.pytest.ini_options] - custom = 1 + [pytest] + custom = "1" value = [ ] # this is here on purpose, as it makes this an invalid '.ini' file """, @@ -777,6 +777,29 @@ def pytest_addoption(parser): config = pytester.parseconfig("--config-file", "custom.toml") assert config.getini("custom") == "1" + def test_custom_toml_config_file_is_loaded(self, pytester: Pytester) -> None: + pytester.makepyfile( + bench_example=""" + def bench_example(): + pass + """ + ) + pytester.makefile( + ".toml", + pytest_benchmark=""" + [pytest] + python_files = ["bench_*.py"] + python_functions = ["bench_*"] + """, + ) + + result = pytester.runpytest( + "-c", "pytest_benchmark.toml", "--collect-only", "-q" + ) + + assert result.ret == ExitCode.OK + result.stdout.fnmatch_lines(["bench_example.py::bench_example"]) + def test_absolute_win32_path(self, pytester: Pytester) -> None: temp_ini_file = pytester.makeini("[pytest]") from os.path import normpath diff --git a/testing/test_findpaths.py b/testing/test_findpaths.py index aea7b1f9a4d..63345fb3051 100644 --- a/testing/test_findpaths.py +++ b/testing/test_findpaths.py @@ -73,7 +73,7 @@ def test_invalid_toml_file(self, tmp_path: Path) -> None: load_config_dict_from_file(fn) def test_custom_toml_file(self, tmp_path: Path) -> None: - """.toml files without [tool.pytest] are not considered for configuration.""" + """Custom .toml files without [pytest] have no configuration values.""" fn = tmp_path / "myconfig.toml" fn.write_text( dedent( @@ -84,12 +84,11 @@ def test_custom_toml_file(self, tmp_path: Path) -> None: ), encoding="utf-8", ) - assert load_config_dict_from_file(fn) is None + assert load_config_dict_from_file(fn) == {} - def test_valid_toml_file(self, tmp_path: Path) -> None: - """.toml files with [tool.pytest.ini_options] are read correctly, including changing - data types to str/list for compatibility with other configuration options.""" - fn = tmp_path / "myconfig.toml" + def test_pyproject_toml_ini_options(self, tmp_path: Path) -> None: + """[tool.pytest.ini_options] values are converted for INI compatibility.""" + fn = tmp_path / "pyproject.toml" fn.write_text( dedent( """ @@ -111,7 +110,35 @@ def test_valid_toml_file(self, tmp_path: Path) -> None: "heterogeneous_array": ConfigValue([1, "str"], origin="file", mode="ini"), } - def test_native_toml_config(self, tmp_path: Path) -> None: + def test_custom_toml_native_config(self, tmp_path: Path) -> None: + """Custom .toml files read native configuration from [pytest].""" + fn = tmp_path / "myconfig.toml" + fn.write_text( + dedent( + """ + [pytest] + xfail_strict = true + testpaths = ["tests", "integration"] + """ + ), + encoding="utf-8", + ) + + assert load_config_dict_from_file(fn) == { + "xfail_strict": ConfigValue(True, origin="file", mode="toml"), + "testpaths": ConfigValue( + ["tests", "integration"], origin="file", mode="toml" + ), + } + + def test_custom_toml_ignores_pyproject_table(self, tmp_path: Path) -> None: + """Custom .toml files do not read configuration from [tool.pytest].""" + fn = tmp_path / "myconfig.toml" + fn.write_text("[tool.pytest]\nxfail_strict = true", encoding="utf-8") + + assert load_config_dict_from_file(fn) == {} + + def test_pyproject_native_toml_config(self, tmp_path: Path) -> None: """[tool.pytest] sections with native types are parsed correctly without coercion.""" fn = tmp_path / "pyproject.toml" fn.write_text(