From 18fbe172634f77cd1ccf2d8de3511fe57f87be59 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 26 Oct 2025 20:16:40 +0200 Subject: [PATCH 1/5] doc/reference: add a couple of missing confval entries --- doc/en/how-to/logging.rst | 2 +- doc/en/reference/reference.rst | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/doc/en/how-to/logging.rst b/doc/en/how-to/logging.rst index 300e9f6e6c2..a40aeb1cbe0 100644 --- a/doc/en/how-to/logging.rst +++ b/doc/en/how-to/logging.rst @@ -230,7 +230,7 @@ option names are: * ``log_file_date_format`` You can call ``set_log_path()`` to customize the log_file path dynamically. This functionality -is considered **experimental**. Note that ``set_log_path()`` respects the ``log_file_mode`` option. +is considered **experimental**. Note that ``set_log_path()`` respects the :confval:`log_file_mode` option. .. _log_colors: diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index d5f8716d7b4..764663fc12e 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -1470,6 +1470,19 @@ passed multiple times. The expected format is ``name=value``. For example:: as this is considered less error prone, see :issue:`3155` for more details. +.. confval:: enable_assertion_pass_hook + + Enables the :hook:`pytest_assertion_pass` hook. + Make sure to delete any previously generated ``.pyc`` cache files. + + .. tab:: ini + + .. code-block:: ini + + [pytest] + enable_assertion_pass_hook = true + + .. confval:: faulthandler_timeout Dumps the tracebacks of all threads if a test takes longer than ``X`` seconds to run (including @@ -1702,6 +1715,21 @@ passed multiple times. The expected format is ``name=value``. For example:: For more information, see :ref:`logging`. +.. confval:: log_file_mode + + Sets the mode that the logging file is opened with. + The options are ``"w"`` to recreate the file (the default) or ``"a"`` to append to the file. + + .. tab:: ini + + .. code-block:: ini + + [pytest] + log_file_mode = a + + For more information, see :ref:`logging`. + + .. confval:: log_file_date_format From cc400d1fba5ca6ce0421880166d0a2948454f6ff Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 26 Oct 2025 18:11:26 +0200 Subject: [PATCH 2/5] config: consider empty .pytest.ini as config file Fix #13849. --- changelog/13849.bugfix.rst | 2 ++ src/_pytest/config/findpaths.py | 2 +- testing/test_config.py | 11 +++++++---- testing/test_findpaths.py | 5 +++-- 4 files changed, 13 insertions(+), 7 deletions(-) create mode 100644 changelog/13849.bugfix.rst diff --git a/changelog/13849.bugfix.rst b/changelog/13849.bugfix.rst new file mode 100644 index 00000000000..cdcc7b83591 --- /dev/null +++ b/changelog/13849.bugfix.rst @@ -0,0 +1,2 @@ +Hidden ``.pytest.ini`` files are now picked up as the config file even if empty. +This was an inconsistency with non-hidden ``pytest.ini``. diff --git a/src/_pytest/config/findpaths.py b/src/_pytest/config/findpaths.py index db0b829a530..5b190d3174b 100644 --- a/src/_pytest/config/findpaths.py +++ b/src/_pytest/config/findpaths.py @@ -68,7 +68,7 @@ def load_config_dict_from_file( } else: # "pytest.ini" files are always the source of configuration, even if empty. - if filepath.name == "pytest.ini": + if filepath.name in {"pytest.ini", ".pytest.ini"}: return {} # '.cfg' files are considered if they contain a "[tool:pytest]" section. diff --git a/testing/test_config.py b/testing/test_config.py index 01907132cd1..34fab5272e8 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -1826,13 +1826,16 @@ def test_with_ini(self, tmp_path: Path, name: str, contents: str) -> None: assert parsed_inipath == inipath assert ini_config["x"] == ConfigValue("10", origin="file") - @pytest.mark.parametrize("name", ["setup.cfg", "tox.ini"]) - def test_pytestini_overrides_empty_other(self, tmp_path: Path, name: str) -> None: - inipath = tmp_path / "pytest.ini" + @pytest.mark.parametrize("pytest_ini", ["pytest.ini", ".pytest.ini"]) + @pytest.mark.parametrize("other", ["setup.cfg", "tox.ini"]) + def test_pytestini_overrides_empty_other( + self, tmp_path: Path, pytest_ini: str, other: str + ) -> None: + inipath = tmp_path / pytest_ini inipath.touch() a = tmp_path / "a" a.mkdir() - (a / name).touch() + (a / other).touch() rootpath, parsed_inipath, *_ = determine_setup( inifile=None, override_ini=None, diff --git a/testing/test_findpaths.py b/testing/test_findpaths.py index e8e70f9c6fb..0ed53a02418 100644 --- a/testing/test_findpaths.py +++ b/testing/test_findpaths.py @@ -15,9 +15,10 @@ class TestLoadConfigDictFromFile: - def test_empty_pytest_ini(self, tmp_path: Path) -> None: + @pytest.mark.parametrize("filename", ["pytest.ini", ".pytest.ini"]) + def test_empty_pytest_ini(self, tmp_path: Path, filename: str) -> None: """pytest.ini files are always considered for configuration, even if empty""" - fn = tmp_path / "pytest.ini" + fn = tmp_path / filename fn.write_text("", encoding="utf-8") assert load_config_dict_from_file(fn) == {} From 147467db1cd00cdb5e1e56b954bd2cf95ddedf4b Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 24 Oct 2025 22:44:03 +0300 Subject: [PATCH 3/5] config: generalize from INI format This prepares the code and documentation for adding another configuration file format, TOML. Add a `mode` field to `ConfigValue` to track the parsing mode. Add tabs to all configuration file snippets in the docs, so that we may show both toml and ini in a nice way once toml is added. Uses the sphinx-inline-tabs package that I saw used by tox documentation. Avoid references to "pytest.ini" and "ini file" in docs, instead refer to "configuration file" (I'm sure I missed some). --- doc/en/conf.py | 1 + doc/en/deprecations.rst | 18 +- doc/en/example/markers.rst | 13 +- doc/en/example/pythoncollection.rst | 57 ++-- doc/en/example/simple.rst | 11 +- doc/en/explanation/goodpractices.rst | 22 +- doc/en/how-to/capture-warnings.rst | 48 ++- doc/en/how-to/doctest.rst | 30 +- doc/en/how-to/fixtures.rst | 11 +- doc/en/how-to/logging.rst | 54 ++-- doc/en/how-to/mark.rst | 40 ++- doc/en/how-to/output.rst | 28 +- doc/en/how-to/parametrize.rst | 10 +- doc/en/how-to/plugins.rst | 18 +- doc/en/how-to/skipping.rst | 8 +- doc/en/how-to/writing_plugins.rst | 11 +- doc/en/reference/customize.rst | 89 +++--- doc/en/reference/reference.rst | 419 ++++++++++++++++----------- doc/en/requirements.txt | 1 + src/_pytest/config/__init__.py | 73 +++-- src/_pytest/config/argparsing.py | 26 +- src/_pytest/config/findpaths.py | 22 +- src/_pytest/fixtures.py | 2 +- src/_pytest/helpconfig.py | 4 +- src/_pytest/hookspec.py | 18 +- src/_pytest/python.py | 2 +- testing/logging/test_reporting.py | 3 +- testing/test_collection.py | 2 +- testing/test_config.py | 24 +- testing/test_conftest.py | 2 +- testing/test_doctest.py | 4 +- testing/test_findpaths.py | 22 +- testing/test_python_path.py | 4 +- testing/test_terminal.py | 2 +- testing/test_warnings.py | 2 +- 35 files changed, 625 insertions(+), 476 deletions(-) diff --git a/doc/en/conf.py b/doc/en/conf.py index c89e14d07fa..81156493131 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -34,6 +34,7 @@ "sphinx.ext.todo", "sphinx.ext.viewcode", "sphinx_removed_in", + "sphinx_inline_tabs", "sphinxcontrib_trio", "sphinxcontrib.towncrier.ext", # provides `towncrier-draft-entries` directive "sphinx_issues", # implements `:issue:`, `:pr:` and other GH-related roles diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index 790e1d89f29..af8bc4108a6 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -845,20 +845,24 @@ that manipulate this type of file (for example, Jenkins, Azure Pipelines, etc.). Users are recommended to try the new ``xunit2`` format and see if their tooling that consumes the JUnit XML file supports it. -To use the new format, update your ``pytest.ini``: +To use the new format, update your configuration file: -.. code-block:: ini +.. tab:: ini - [pytest] - junit_family=xunit2 + .. code-block:: ini + + [pytest] + junit_family = xunit2 If you discover that your tooling does not support the new format, and want to keep using the legacy version, set the option to ``legacy`` instead: -.. code-block:: ini +.. tab:: ini + + .. code-block:: ini - [pytest] - junit_family=legacy + [pytest] + junit_family = legacy By using ``legacy`` you will keep using the legacy/xunit1 format when upgrading to pytest 6.0, where the default format will be ``xunit2``. diff --git a/doc/en/example/markers.rst b/doc/en/example/markers.rst index 071869c07b4..944047c583d 100644 --- a/doc/en/example/markers.rst +++ b/doc/en/example/markers.rst @@ -239,13 +239,14 @@ Registering markers Registering markers for your test suite is simple: -.. code-block:: ini +.. tab:: ini - # content of pytest.ini - [pytest] - markers = - webtest: mark a test as a webtest. - slow: mark test as slow. + .. code-block:: ini + + [pytest] + markers = + webtest: mark a test as a webtest. + slow: mark test as slow. Multiple custom markers can be registered, by defining each one in its own line, as shown in above example. diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index 9aada00345a..e0020384864 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -88,13 +88,14 @@ Example: Changing directory recursion ----------------------------------------------------- -You can set the :confval:`norecursedirs` option in an ini-file, for example your ``pytest.ini`` in the project root directory: +You can set the :confval:`norecursedirs` option in a configuration file: -.. code-block:: ini +.. tab:: ini - # content of pytest.ini - [pytest] - norecursedirs = .svn _build tmp* + .. code-block:: ini + + [pytest] + norecursedirs = .svn _build tmp* This would tell ``pytest`` to not recurse into typical subversion or sphinx-build directories or into any ``tmp`` prefixed directory. @@ -108,14 +109,15 @@ the :confval:`python_files`, :confval:`python_classes` and :confval:`python_functions` in your :ref:`configuration file `. Here is an example: -.. code-block:: ini +.. tab:: ini + + .. code-block:: ini - # content of pytest.ini - # Example 1: have pytest look for "check" instead of "test" - [pytest] - python_files = check_*.py - python_classes = Check - python_functions = *_check + # Example 1: have pytest look for "check" instead of "test" + [pytest] + python_files = check_*.py + python_classes = Check + python_functions = *_check This would make ``pytest`` look for tests in files that match the ``check_* .py`` glob-pattern, ``Check`` prefixes in classes, and functions and methods @@ -152,12 +154,13 @@ The test collection would look like this: You can check for multiple glob patterns by adding a space between the patterns: -.. code-block:: ini +.. tab:: ini + + .. code-block:: ini - # Example 2: have pytest look for files with "test" and "example" - # content of pytest.ini - [pytest] - python_files = test_*.py example_*.py + # Example 2: have pytest look for files with "test" and "example" + [pytest] + python_files = test_*.py example_*.py .. note:: @@ -178,14 +181,15 @@ example if you have unittest2 installed you can type: pytest --pyargs unittest2.test.test_skipping -q which would run the respective test module. Like with -other options, through an ini-file and the :confval:`addopts` option you +other options, through a configuration file and the :confval:`addopts` option you can make this change more permanently: -.. code-block:: ini +.. tab:: ini - # content of pytest.ini - [pytest] - addopts = --pyargs + .. code-block:: ini + + [pytest] + addopts = --pyargs Now a simple invocation of ``pytest NAME`` will check if NAME exists as an importable package/module and otherwise @@ -224,11 +228,12 @@ Customizing test collection You can easily instruct ``pytest`` to discover tests from every Python file: -.. code-block:: ini +.. tab:: ini + + .. code-block:: ini - # content of pytest.ini - [pytest] - python_files = *.py + [pytest] + python_files = *.py However, many projects will have a ``setup.py`` which they don't want to be imported. Moreover, there may files only importable by a specific python diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index c1a444bea18..7c1822efae5 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -11,11 +11,12 @@ every time you use ``pytest``. For example, if you always want to see detailed info on skipped and xfailed tests, as well as have terser "dot" progress output, you can write it into a configuration file: -.. code-block:: ini +.. tab:: ini - # content of pytest.ini - [pytest] - addopts = -ra -q + .. code-block:: ini + + [pytest] + addopts = -ra -q Alternatively, you can set a ``PYTEST_ADDOPTS`` environment variable to add command @@ -29,7 +30,7 @@ Here's how the command-line is built in the presence of ``addopts`` or the envir .. code-block:: text - $PYTEST_ADDOPTS + $PYTEST_ADDOPTS So if the user executes in the command-line: diff --git a/doc/en/explanation/goodpractices.rst b/doc/en/explanation/goodpractices.rst index 51c0b960aed..70a2e1d3872 100644 --- a/doc/en/explanation/goodpractices.rst +++ b/doc/en/explanation/goodpractices.rst @@ -94,14 +94,14 @@ This has the following benefits: For new projects, we recommend to use ``importlib`` :ref:`import mode ` (see which-import-mode_ for a detailed explanation). -To this end, add the following to your ``pyproject.toml``: +To this end, add the following to your configuration file: -.. code-block:: toml +.. tab:: ini + + .. code-block:: ini - [tool.pytest.ini_options] - addopts = [ - "--import-mode=importlib", - ] + [pytest] + addopts = --import-mode=importlib .. _src-layout: @@ -126,12 +126,14 @@ which are better explained in this excellent `blog post`_ by Ionel Cristian Măr PYTHONPATH=src pytest or in a permanent manner by using the :confval:`pythonpath` configuration variable and adding the - following to your ``pyproject.toml``: + following to your configuration file: + + .. tab:: ini - .. code-block:: toml + .. code-block:: ini - [tool.pytest.ini_options] - pythonpath = "src" + [pytest] + pythonpath = src .. note:: diff --git a/doc/en/how-to/capture-warnings.rst b/doc/en/how-to/capture-warnings.rst index a9bd894b6fd..71a5cd6f9b8 100644 --- a/doc/en/how-to/capture-warnings.rst +++ b/doc/en/how-to/capture-warnings.rst @@ -80,30 +80,20 @@ as an error: FAILED test_show_warnings.py::test_one - UserWarning: api v1, should use ... 1 failed in 0.12s -The same option can be set in the ``pytest.ini`` or ``pyproject.toml`` file using the -``filterwarnings`` ini option. For example, the configuration below will ignore all +The same option can be set in the configuration file using the +:confval:`filterwarnings` configuration option. For example, the configuration below will ignore all user warnings and specific deprecation warnings matching a regex, but will transform all other warnings into errors. -.. code-block:: ini +.. tab:: ini - # pytest.ini - [pytest] - filterwarnings = - error - ignore::UserWarning - ignore:function ham\(\) is deprecated:DeprecationWarning - -.. code-block:: toml + .. code-block:: ini - # pyproject.toml - [tool.pytest.ini_options] - filterwarnings = [ - "error", - "ignore::UserWarning", - # note the use of single quote below to denote "raw" strings in TOML - 'ignore:function ham\(\) is deprecated:DeprecationWarning', - ] + [pytest] + filterwarnings = + error + ignore::UserWarning + ignore:function ham\(\) is deprecated:DeprecationWarning When a warning matches more than one option in the list, the action for the last matching option @@ -112,7 +102,7 @@ is performed. .. note:: - The ``-W`` flag and the ``filterwarnings`` ini option use warning filters that are + The ``-W`` flag and the :confval:`filterwarnings` configuration option use warning filters that are similar in structure, but each configuration option interprets its filter differently. For example, *message* in ``filterwarnings`` is a string containing a regular expression that the start of the warning message must match, @@ -169,7 +159,7 @@ You can specify multiple filters with separate decorators: Filters applied using a mark take precedence over filters passed on the command line or configured -by the :confval:`filterwarnings` ini option. +by the :confval:`filterwarnings` configuration option. You may apply a filter to all tests of a class by using the :ref:`filterwarnings ` mark as a class decorator or to all tests in a module by setting the :globalvar:`pytestmark` variable: @@ -202,7 +192,9 @@ warning summary entirely from the test run output. Disabling warning capture entirely ---------------------------------- -This plugin is enabled by default but can be disabled entirely in your ``pytest.ini`` file with: +This plugin is enabled by default but can be disabled entirely in your configuration file with: + +.. tab:: ini .. code-block:: ini @@ -227,16 +219,18 @@ However, in the specific case where users capture any type of warnings in their no warning will be displayed at all. Sometimes it is useful to hide some specific deprecation warnings that happen in code that you have no control over -(such as third-party libraries), in which case you might use the warning filters options (ini or marks) to ignore +(such as third-party libraries), in which case you might use the warning filters options (configuration or marks) to ignore those warnings. For example: -.. code-block:: ini +.. tab:: ini - [pytest] - filterwarnings = - ignore:.*U.*mode is deprecated:DeprecationWarning + .. code-block:: ini + + [pytest] + filterwarnings = + ignore:.*U.*mode is deprecated:DeprecationWarning This will ignore all warnings of type ``DeprecationWarning`` where the start of the message matches diff --git a/doc/en/how-to/doctest.rst b/doc/en/how-to/doctest.rst index 0a778a8a246..981abc31aa6 100644 --- a/doc/en/how-to/doctest.rst +++ b/doc/en/how-to/doctest.rst @@ -68,13 +68,14 @@ and functions, including from test modules: ============================ 2 passed in 0.12s ============================= You can make these changes permanent in your project by -putting them into a pytest.ini file like this: +putting them into a configuration file like this: -.. code-block:: ini +.. tab:: ini - # content of pytest.ini - [pytest] - addopts = --doctest-modules + .. code-block:: ini + + [pytest] + addopts = --doctest-modules Encoding @@ -82,13 +83,14 @@ Encoding The default encoding is **UTF-8**, but you can specify the encoding that will be used for those doctest files using the -``doctest_encoding`` ini option: +:confval:`doctest_encoding` configuration option: + +.. tab:: ini -.. code-block:: ini + .. code-block:: ini - # content of pytest.ini - [pytest] - doctest_encoding = latin1 + [pytest] + doctest_encoding = latin1 .. _using doctest options: @@ -102,10 +104,12 @@ configuration file. For example, to make pytest ignore trailing whitespaces and ignore lengthy exception stack traces you can just write: -.. code-block:: ini +.. tab:: ini + + .. code-block:: ini - [pytest] - doctest_optionflags = NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL + [pytest] + doctest_optionflags = NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL Alternatively, options can be enabled by an inline comment in the doc test itself: diff --git a/doc/en/how-to/fixtures.rst b/doc/en/how-to/fixtures.rst index 7f1a7610bb8..974c460d5f3 100644 --- a/doc/en/how-to/fixtures.rst +++ b/doc/en/how-to/fixtures.rst @@ -1736,13 +1736,14 @@ and you may specify fixture usage at the test module level using :globalvar:`pyt It is also possible to put fixtures required by all tests in your project -into an ini-file: +into a configuration file: -.. code-block:: ini +.. tab:: ini - # content of pytest.ini - [pytest] - usefixtures = cleandir + .. code-block:: ini + + [pytest] + usefixtures = cleandir .. warning:: diff --git a/doc/en/how-to/logging.rst b/doc/en/how-to/logging.rst index a40aeb1cbe0..8e636dea6b6 100644 --- a/doc/en/how-to/logging.rst +++ b/doc/en/how-to/logging.rst @@ -47,13 +47,15 @@ Shows failed tests like so: text going to stderr ==================== 2 failed in 0.02 seconds ===================== -These options can also be customized through ``pytest.ini`` file: +These options can also be customized through a configuration file: -.. code-block:: ini +.. tab:: ini - [pytest] - log_format = %(asctime)s %(levelname)s %(message)s - log_date_format = %Y-%m-%d %H:%M:%S + .. code-block:: ini + + [pytest] + log_format = %(asctime)s %(levelname)s %(message)s + log_date_format = %Y-%m-%d %H:%M:%S Specific loggers can be disabled via ``--log-disable={logger_name}``. This argument can be passed multiple times: @@ -198,12 +200,12 @@ Additionally, you can also specify ``--log-cli-format`` and ``--log-date-format`` if not provided, but are applied only to the console logging handler. -All of the CLI log options can also be set in the configuration INI file. The +All of the CLI log options can also be set in the configuration file. The option names are: -* ``log_cli_level`` -* ``log_cli_format`` -* ``log_cli_date_format`` +* :confval:`log_cli_level` +* :confval:`log_cli_format` +* :confval:`log_cli_date_format` If you need to record the whole test suite logging calls to a file, you can pass ``--log-file=/path/to/log/file``. This log file is opened in write mode by default which @@ -220,14 +222,14 @@ Additionally, you can also specify ``--log-file-format`` and ``--log-file-date-format`` which are equal to ``--log-format`` and ``--log-date-format`` but are applied to the log file logging handler. -All of the log file options can also be set in the configuration INI file. The +All of the log file options can also be set in the configuration file. The option names are: -* ``log_file`` -* ``log_file_mode`` -* ``log_file_level`` -* ``log_file_format`` -* ``log_file_date_format`` +* :confval:`log_file` +* :confval:`log_file_mode` +* :confval:`log_file_level` +* :confval:`log_file_format` +* :confval:`log_file_date_format` You can call ``set_log_path()`` to customize the log_file path dynamically. This functionality is considered **experimental**. Note that ``set_log_path()`` respects the :confval:`log_file_mode` option. @@ -266,12 +268,14 @@ This feature was introduced as a drop-in replacement for the with each other. The backward compatibility API with ``pytest-capturelog`` has been dropped when this feature was introduced, so if for that reason you still need ``pytest-catchlog`` you can disable the internal feature by -adding to your ``pytest.ini``: +adding to your configuration file: + +.. tab:: ini -.. code-block:: ini + .. code-block:: ini - [pytest] - addopts=-p no:logging + [pytest] + addopts = -p no:logging .. _log_changes_3_4: @@ -293,13 +297,15 @@ made in ``3.4`` after community feedback: * :ref:`Live Logs ` are now sent to ``sys.stdout`` and no longer require the ``-s`` command-line option to work. -If you want to partially restore the logging behavior of version ``3.3``, you can add this options to your ``ini`` +If you want to partially restore the logging behavior of version ``3.3``, you can add this options to your configuration file: -.. code-block:: ini +.. tab:: ini + + .. code-block:: ini - [pytest] - log_cli=true - log_level=NOTSET + [pytest] + log_cli = true + log_level = NOTSET More details about the discussion that lead to this changes can be read in :issue:`3013`. diff --git a/doc/en/how-to/mark.rst b/doc/en/how-to/mark.rst index 40ee14c36fd..36b4058af8f 100644 --- a/doc/en/how-to/mark.rst +++ b/doc/en/how-to/mark.rst @@ -34,24 +34,16 @@ See :ref:`mark examples` for examples which also serve as documentation. Registering marks ----------------- -You can register custom marks in your ``pytest.ini`` file like this: +You can register custom marks in your configuration file like this: -.. code-block:: ini +.. tab:: ini - [pytest] - markers = - slow: marks tests as slow (deselect with '-m "not slow"') - serial + .. code-block:: ini -or in your ``pyproject.toml`` file like this: - -.. code-block:: toml - - [tool.pytest.ini_options] - markers = [ - "slow: marks tests as slow (deselect with '-m \"not slow\"')", - "serial", - ] + [pytest] + markers = + slow: marks tests as slow (deselect with '-m "not slow"') + serial Note that everything past the ``:`` after the mark name is an optional description. @@ -77,17 +69,19 @@ Raising errors on unknown marks Unregistered marks applied with the ``@pytest.mark.name_of_the_mark`` decorator will always emit a warning in order to avoid silently doing something surprising due to mistyped names. As described in the previous section, you can disable -the warning for custom marks by registering them in your ``pytest.ini`` file or +the warning for custom marks by registering them in your configuration file or using a custom ``pytest_configure`` hook. -When the :confval:`strict_markers` ini option is set, any unknown marks applied +When the :confval:`strict_markers` configuration option is set, any unknown marks applied with the ``@pytest.mark.name_of_the_mark`` decorator will trigger an error. You can enforce this validation in your project by setting :confval:`strict_markers` in your configuration: -.. code-block:: ini +.. tab:: ini + + .. code-block:: ini - [pytest] - strict_markers = True - markers = - slow: marks tests as slow (deselect with '-m "not slow"') - serial + [pytest] + strict_markers = true + markers = + slow: marks tests as slow (deselect with '-m "not slow"') + serial diff --git a/doc/en/how-to/output.rst b/doc/en/how-to/output.rst index bc98b816484..5fc437c44e0 100644 --- a/doc/en/how-to/output.rst +++ b/doc/en/how-to/output.rst @@ -558,13 +558,15 @@ Modifying truncation limits .. versionadded: 8.4 Default truncation limits are 8 lines or 640 characters, whichever comes first. -To set custom truncation limits you can use following ``pytest.ini`` file options: +To set custom truncation limits you can use the following configuration file options: -.. code-block:: ini +.. tab:: ini - [pytest] - truncation_limit_lines = 10 - truncation_limit_chars = 90 + .. code-block:: ini + + [pytest] + truncation_limit_lines = 10 + truncation_limit_chars = 90 That will cause pytest to truncate the assertions to 10 lines or 90 characters, whichever comes first. @@ -588,10 +590,12 @@ to create an XML file at ``path``. To set the name of the root test suite xml item, you can configure the ``junit_suite_name`` option in your config file: -.. code-block:: ini +.. tab:: ini + + .. code-block:: ini - [pytest] - junit_suite_name = my_suite + [pytest] + junit_suite_name = my_suite .. versionadded:: 4.0 @@ -602,10 +606,12 @@ should report total test execution times, including setup and teardown It is the default pytest behavior. To report just call durations instead, configure the ``junit_duration_report`` option like this: -.. code-block:: ini +.. tab:: ini + + .. code-block:: ini - [pytest] - junit_duration_report = call + [pytest] + junit_duration_report = call .. _record_property example: diff --git a/doc/en/how-to/parametrize.rst b/doc/en/how-to/parametrize.rst index fe186146434..bd83fe7b5df 100644 --- a/doc/en/how-to/parametrize.rst +++ b/doc/en/how-to/parametrize.rst @@ -88,12 +88,14 @@ them in turn: for the parametrization because it has several downsides. If however you would like to use unicode strings in parametrization and see them in the terminal as is (non-escaped), use this option - in your ``pytest.ini``: + in your configuration file: - .. code-block:: ini + .. tab:: ini - [pytest] - disable_test_id_escaping_and_forfeit_all_rights_to_community_support = True + .. code-block:: ini + + [pytest] + disable_test_id_escaping_and_forfeit_all_rights_to_community_support = true Keep in mind however that this might cause unwanted side effects and even bugs depending on the OS used and plugins currently installed, diff --git a/doc/en/how-to/plugins.rst b/doc/en/how-to/plugins.rst index b2cb1cda5c3..74e309c5be9 100644 --- a/doc/en/how-to/plugins.rst +++ b/doc/en/how-to/plugins.rst @@ -120,12 +120,14 @@ This means that any subsequent try to activate/load the named plugin will not work. If you want to unconditionally disable a plugin for a project, you can add -this option to your ``pytest.ini`` file: +this option to your configuration file: -.. code-block:: ini +.. tab:: ini - [pytest] - addopts = -p no:NAME + .. code-block:: ini + + [pytest] + addopts = -p no:NAME Alternatively to disable it only in certain environments (for example in a CI server), you can set ``PYTEST_ADDOPTS`` environment variable to @@ -151,10 +153,12 @@ manually specify each plugin with ``-p`` or :envvar:`PYTEST_PLUGINS`, you can us pytest --disable-plugin-autoload -p NAME,NAME2 -.. code-block:: ini +.. tab:: ini + + .. code-block:: ini - [pytest] - addopts = + [pytest] + addopts = --disable-plugin-autoload -p NAME -p NAME2 diff --git a/doc/en/how-to/skipping.rst b/doc/en/how-to/skipping.rst index 10c45c23ed2..ab68587c8ff 100644 --- a/doc/en/how-to/skipping.rst +++ b/doc/en/how-to/skipping.rst @@ -333,10 +333,12 @@ This will make ``XPASS`` ("unexpectedly passing") results from this test to fail You can change the default value of the ``strict`` parameter using the ``strict_xfail`` ini option: -.. code-block:: ini +.. tab:: ini - [pytest] - strict_xfail=true + .. code-block:: ini + + [pytest] + strict_xfail = true Ignoring xfail diff --git a/doc/en/how-to/writing_plugins.rst b/doc/en/how-to/writing_plugins.rst index 1bba9644649..71d42140e8d 100644 --- a/doc/en/how-to/writing_plugins.rst +++ b/doc/en/how-to/writing_plugins.rst @@ -420,13 +420,14 @@ before running pytest on it. This way we can abstract the tested logic to separa which is especially useful for longer tests and/or longer ``conftest.py`` files. Note that for ``pytester.copy_example`` to work we need to set `pytester_example_dir` -in our ``pytest.ini`` to tell pytest where to look for example files. +in our configuration file to tell pytest where to look for example files. -.. code-block:: ini +.. tab:: ini - # content of pytest.ini - [pytest] - pytester_example_dir = . + .. code-block:: ini + + [pytest] + pytester_example_dir = . .. code-block:: python diff --git a/doc/en/reference/customize.rst b/doc/en/reference/customize.rst index 373223ec913..2e5d4a7356f 100644 --- a/doc/en/reference/customize.rst +++ b/doc/en/reference/customize.rst @@ -4,8 +4,7 @@ Configuration Command line options and configuration file settings ----------------------------------------------------------------- -You can get help on command line options and values in INI-style -configurations files by using the general help option: +You can get help on command line and configuration options by using the general help option: .. code-block:: bash @@ -31,15 +30,17 @@ pytest.ini Alternatively, the hidden version ``.pytest.ini`` can be used. -.. code-block:: ini +.. tab:: ini - # pytest.ini or .pytest.ini - [pytest] - minversion = 6.0 - addopts = -ra -q - testpaths = - tests - integration + .. code-block:: ini + + # pytest.ini or .pytest.ini + [pytest] + minversion = 6.0 + addopts = -ra -q + testpaths = + tests + integration pyproject.toml @@ -47,28 +48,20 @@ pyproject.toml .. versionadded:: 6.0 -``pyproject.toml`` are considered for configuration when they contain a ``tool.pytest.ini_options`` table. - -.. code-block:: toml - - # pyproject.toml - [tool.pytest.ini_options] - minversion = "6.0" - addopts = "-ra -q" - testpaths = [ - "tests", - "integration", - ] +``pyproject.toml`` files are supported for configuration. -.. note:: +.. tab:: ini - One might wonder why ``[tool.pytest.ini_options]`` instead of ``[tool.pytest]`` as is the - case with other tools. + .. code-block:: toml - The reason is that the pytest team intends to fully utilize the rich TOML data format - for configuration in the future, reserving the ``[tool.pytest]`` table for that. - The ``ini_options`` table is being used, for now, as a bridge between the existing - ``.ini`` configuration system and the future configuration format. + # pyproject.toml + [tool.pytest.ini_options] + minversion = "6.0" + addopts = "-ra -q" + testpaths = [ + "tests", + "integration", + ] tox.ini ~~~~~~~ @@ -76,15 +69,17 @@ tox.ini ``tox.ini`` files are the configuration files of the `tox `__ project, and can also be used to hold pytest configuration if they have a ``[pytest]`` section. -.. code-block:: ini +.. tab:: ini - # tox.ini - [pytest] - minversion = 6.0 - addopts = -ra -q - testpaths = - tests - integration + .. code-block:: ini + + # tox.ini + [pytest] + minversion = 6.0 + addopts = -ra -q + testpaths = + tests + integration setup.cfg @@ -93,15 +88,17 @@ setup.cfg ``setup.cfg`` files are general purpose configuration files, used originally by ``distutils`` (now deprecated) and :std:doc:`setuptools `, and can also be used to hold pytest configuration if they have a ``[tool:pytest]`` section. -.. code-block:: ini +.. tab:: ini + + .. code-block:: ini - # setup.cfg - [tool:pytest] - minversion = 6.0 - addopts = -ra -q - testpaths = - tests - integration + # setup.cfg + [tool:pytest] + minversion = 6.0 + addopts = -ra -q + testpaths = + tests + integration .. warning:: @@ -138,7 +135,7 @@ influence how modules are imported. See :ref:`pythonpath` for more details. The ``--rootdir=path`` command-line option can be used to force a specific directory. Note that contrary to other command-line options, ``--rootdir`` cannot be used with -:confval:`addopts` inside ``pytest.ini`` because the ``rootdir`` is used to *find* ``pytest.ini`` +:confval:`addopts` inside a configuration file because the ``rootdir`` is used to *find* the configuration file already. Finding the ``rootdir`` diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index 764663fc12e..3c25d1de3a0 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -1318,13 +1318,14 @@ passed multiple times. The expected format is ``name=value``. For example:: .. confval:: addopts Add the specified ``OPTS`` to the set of command line arguments as if they - had been specified by the user. Example: if you have this ini file content: + had been specified by the user. Example: if you have this configuration file content: - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - # content of pytest.ini - [pytest] - addopts = --maxfail=2 -rf # exit after 2 failures, report fail info + [pytest] + addopts = --maxfail=2 -rf # exit after 2 failures, report fail info issuing ``pytest test_hello.py`` actually means: @@ -1351,10 +1352,12 @@ passed multiple times. The expected format is ``name=value``. For example:: Setting this to ``false`` will make pytest collect classes/functions from test files **only** if they are defined in that file (as opposed to imported there). - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - [pytest] - collect_imported_tests = false + [pytest] + collect_imported_tests = false Default: ``true`` @@ -1404,11 +1407,12 @@ passed multiple times. The expected format is ``name=value``. For example:: The default is ``progress``, but you can fallback to ``classic`` if you prefer or the new mode is causing unexpected problems: - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - # content of pytest.ini - [pytest] - console_output_style = classic + [pytest] + console_output_style = classic .. confval:: disable_test_id_escaping_and_forfeit_all_rights_to_community_support @@ -1419,12 +1423,14 @@ passed multiple times. The expected format is ``name=value``. For example:: for the parametrization because it has several downsides. If however you would like to use unicode strings in parametrization and see them in the terminal as is (non-escaped), use this option - in your ``pytest.ini``: + in your configuration file: - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - [pytest] - disable_test_id_escaping_and_forfeit_all_rights_to_community_support = True + [pytest] + disable_test_id_escaping_and_forfeit_all_rights_to_community_support = true Keep in mind however that this might cause unwanted side effects and even bugs depending on the OS used and plugins currently installed, @@ -1458,11 +1464,12 @@ passed multiple times. The expected format is ``name=value``. For example:: * ``xfail`` marks tests with an empty parameterset as xfail(run=False) * ``fail_at_collect`` raises an exception if parametrize collects an empty parameter set - .. code-block:: ini + .. tab:: ini - # content of pytest.ini - [pytest] - empty_parameter_set_mark = xfail + .. code-block:: ini + + [pytest] + empty_parameter_set_mark = xfail .. note:: @@ -1489,11 +1496,12 @@ passed multiple times. The expected format is ``name=value``. For example:: fixture setup and teardown). Implemented using the :func:`faulthandler.dump_traceback_later` function, so all caveats there apply. - .. code-block:: ini + .. tab:: ini - # content of pytest.ini - [pytest] - faulthandler_timeout=5 + .. code-block:: ini + + [pytest] + faulthandler_timeout = 5 For more information please refer to :ref:`faulthandler`. @@ -1507,12 +1515,13 @@ passed multiple times. The expected format is ``name=value``. For example:: This option is set to 'false' by default. - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - # content of pytest.ini - [pytest] - faulthandler_timeout=5 - faulthandler_exit_on_timeout=true + [pytest] + faulthandler_timeout = 5 + faulthandler_exit_on_timeout = true For more information please refer to :ref:`faulthandler`. @@ -1524,13 +1533,14 @@ passed multiple times. The expected format is ``name=value``. For example:: warnings. By default all warnings emitted during the test session will be displayed in a summary at the end of the test session. - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - # content of pytest.ini - [pytest] - filterwarnings = - error - ignore::DeprecationWarning + [pytest] + filterwarnings = + error + ignore::DeprecationWarning This tells pytest to ignore deprecation warnings and turn all other warnings into errors. For more information please refer to :ref:`warnings`. @@ -1545,10 +1555,12 @@ passed multiple times. The expected format is ``name=value``. For example:: * ``total`` (the default): duration times reported include setup, call, and teardown times. * ``call``: duration times reported include only call times, excluding setup and teardown. - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - [pytest] - junit_duration_report = call + [pytest] + junit_duration_report = call .. confval:: junit_family @@ -1562,10 +1574,12 @@ passed multiple times. The expected format is ``name=value``. For example:: * ``xunit1`` (or ``legacy``): produces old style output, compatible with the xunit 1.0 format. * ``xunit2``: produces `xunit 2.0 style output `__, which should be more compatible with latest Jenkins versions. **This is the default**. - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - [pytest] - junit_family = xunit2 + [pytest] + junit_family = xunit2 .. confval:: junit_logging @@ -1583,10 +1597,12 @@ passed multiple times. The expected format is ``name=value``. For example:: * ``all``: write captured ``logging``, ``stdout`` and ``stderr`` contents. * ``no`` (the default): no captured output is written. - .. code-block:: ini + .. tab:: ini - [pytest] - junit_logging = system-out + .. code-block:: ini + + [pytest] + junit_logging = system-out .. confval:: junit_log_passing_tests @@ -1596,20 +1612,24 @@ passed multiple times. The expected format is ``name=value``. For example:: If ``junit_logging != "no"``, configures if the captured output should be written to the JUnit XML file for **passing** tests. Default is ``True``. - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - [pytest] - junit_log_passing_tests = False + [pytest] + junit_log_passing_tests = False .. confval:: junit_suite_name To set the name of the root test suite xml item, you can configure the ``junit_suite_name`` option in your config file: - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - [pytest] - junit_suite_name = my_suite + [pytest] + junit_suite_name = my_suite .. confval:: log_auto_indent @@ -1624,10 +1644,12 @@ passed multiple times. The expected format is ``name=value``. For example:: * False or "Off" or 0 - Do not auto-indent multiline log messages (the default behavior) * [positive integer] - auto-indent multiline log messages by [value] spaces - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - [pytest] - log_auto_indent = False + [pytest] + log_auto_indent = false Supports passing kwarg ``extra={"auto_indent": [value]}`` to calls to ``logging.log()`` to specify auto-indentation behavior for @@ -1639,10 +1661,12 @@ passed multiple times. The expected format is ``name=value``. For example:: Enable log display during test run (also known as :ref:`"live logging" `). The default is ``False``. - .. code-block:: ini + .. tab:: ini - [pytest] - log_cli = True + .. code-block:: ini + + [pytest] + log_cli = true .. confval:: log_cli_date_format @@ -1650,10 +1674,12 @@ passed multiple times. The expected format is ``name=value``. For example:: Sets a :py:func:`time.strftime`-compatible string that will be used when formatting dates for live logging. - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - [pytest] - log_cli_date_format = %Y-%m-%d %H:%M:%S + [pytest] + log_cli_date_format = %Y-%m-%d %H:%M:%S For more information, see :ref:`live_logs`. @@ -1663,10 +1689,12 @@ passed multiple times. The expected format is ``name=value``. For example:: Sets a :py:mod:`logging`-compatible string used to format live logging messages. - .. code-block:: ini + .. tab:: ini - [pytest] - log_cli_format = %(asctime)s %(levelname)s %(message)s + .. code-block:: ini + + [pytest] + log_cli_format = %(asctime)s %(levelname)s %(message)s For more information, see :ref:`live_logs`. @@ -1678,10 +1706,12 @@ passed multiple times. The expected format is ``name=value``. For example:: Sets the minimum log message level that should be captured for live logging. The integer value or the names of the levels can be used. - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - [pytest] - log_cli_level = INFO + [pytest] + log_cli_level = INFO For more information, see :ref:`live_logs`. @@ -1692,10 +1722,12 @@ passed multiple times. The expected format is ``name=value``. For example:: Sets a :py:func:`time.strftime`-compatible string that will be used when formatting dates for logging capture. - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - [pytest] - log_date_format = %Y-%m-%d %H:%M:%S + [pytest] + log_date_format = %Y-%m-%d %H:%M:%S For more information, see :ref:`logging`. @@ -1707,10 +1739,12 @@ passed multiple times. The expected format is ``name=value``. For example:: Sets a file name relative to the current working directory where log messages should be written to, in addition to the other logging facilities that are active. - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - [pytest] - log_file = logs/pytest-logs.txt + [pytest] + log_file = logs/pytest-logs.txt For more information, see :ref:`logging`. @@ -1736,10 +1770,12 @@ passed multiple times. The expected format is ``name=value``. For example:: Sets a :py:func:`time.strftime`-compatible string that will be used when formatting dates for the logging file. - .. code-block:: ini + .. tab:: ini - [pytest] - log_file_date_format = %Y-%m-%d %H:%M:%S + .. code-block:: ini + + [pytest] + log_file_date_format = %Y-%m-%d %H:%M:%S For more information, see :ref:`logging`. @@ -1749,10 +1785,12 @@ passed multiple times. The expected format is ``name=value``. For example:: Sets a :py:mod:`logging`-compatible string used to format logging messages redirected to the logging file. - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - [pytest] - log_file_format = %(asctime)s %(levelname)s %(message)s + [pytest] + log_file_format = %(asctime)s %(levelname)s %(message)s For more information, see :ref:`logging`. @@ -1763,10 +1801,12 @@ passed multiple times. The expected format is ``name=value``. For example:: Sets the minimum log message level that should be captured for the logging file. The integer value or the names of the levels can be used. - .. code-block:: ini + .. tab:: ini - [pytest] - log_file_level = INFO + .. code-block:: ini + + [pytest] + log_file_level = INFO For more information, see :ref:`logging`. @@ -1777,10 +1817,12 @@ passed multiple times. The expected format is ``name=value``. For example:: Sets a :py:mod:`logging`-compatible string used to format captured logging messages. - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - [pytest] - log_format = %(asctime)s %(levelname)s %(message)s + [pytest] + log_format = %(asctime)s %(levelname)s %(message)s For more information, see :ref:`logging`. @@ -1792,10 +1834,12 @@ passed multiple times. The expected format is ``name=value``. For example:: Sets the minimum log message level that should be captured for logging capture. The integer value or the names of the levels can be used. - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - [pytest] - log_level = INFO + [pytest] + log_level = INFO For more information, see :ref:`logging`. @@ -1806,27 +1850,30 @@ passed multiple times. The expected format is ``name=value``. For example:: only known markers - defined in code by core pytest or some plugin - are allowed. You can list additional markers in this setting to add them to the whitelist, - in which case you probably want to set :confval:`strict_markers` to ``True`` + in which case you probably want to set :confval:`strict_markers` to ``true`` to avoid future regressions: - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - [pytest] - strict_markers = True - markers = - slow - serial + [pytest] + strict_markers = true + markers = + slow + serial .. confval:: minversion Specifies a minimal pytest version required for running tests. - .. code-block:: ini + .. tab:: ini - # content of pytest.ini - [pytest] - minversion = 3.0 # will fail if we run with pytest-2.8 + .. code-block:: ini + + [pytest] + minversion = 3.0 # will fail if we run with pytest-2.8 .. confval:: norecursedirs @@ -1846,10 +1893,12 @@ passed multiple times. The expected format is ``name=value``. For example:: Setting a ``norecursedirs`` replaces the default. Here is an example of how to avoid certain directories: - .. code-block:: ini + .. tab:: ini - [pytest] - norecursedirs = .svn _build tmp* + .. code-block:: ini + + [pytest] + norecursedirs = .svn _build tmp* This would tell ``pytest`` to not look into typical subversion or sphinx-build directories or into any ``tmp`` prefixed directory. @@ -1872,10 +1921,12 @@ passed multiple times. The expected format is ``name=value``. For example:: class prefixed with ``Test`` as a test collection. Here is an example of how to collect tests from classes that end in ``Suite``: - .. code-block:: ini + .. tab:: ini - [pytest] - python_classes = *Suite + .. code-block:: ini + + [pytest] + python_classes = *Suite Note that ``unittest.TestCase`` derived classes are always collected regardless of this option, as ``unittest``'s own collection framework is used @@ -1888,20 +1939,22 @@ passed multiple times. The expected format is ``name=value``. For example:: are considered as test modules. Search for multiple glob patterns by adding a space between patterns: - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - [pytest] - python_files = test_*.py check_*.py example_*.py + [pytest] + python_files = test_*.py check_*.py example_*.py - Or one per line: + Or one per line: - .. code-block:: ini + .. code-block:: ini - [pytest] - python_files = - test_*.py - check_*.py - example_*.py + [pytest] + python_files = + test_*.py + check_*.py + example_*.py By default, files matching ``test_*.py`` and ``*_test.py`` will be considered test modules. @@ -1915,10 +1968,12 @@ passed multiple times. The expected format is ``name=value``. For example:: function prefixed with ``test`` as a test. Here is an example of how to collect test functions and methods that end in ``_test``: - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - [pytest] - python_functions = *_test + [pytest] + python_functions = *_test Note that this has no effect on methods that live on a ``unittest.TestCase`` derived class, as ``unittest``'s own collection framework is used @@ -1936,10 +1991,12 @@ passed multiple times. The expected format is ``name=value``. For example:: Paths are relative to the :ref:`rootdir ` directory. Directories remain in path for the duration of the test session. - .. code-block:: ini + .. tab:: ini - [pytest] - pythonpath = src1 src2 + .. code-block:: ini + + [pytest] + pythonpath = src1 src2 .. confval:: required_plugins @@ -1949,10 +2006,12 @@ passed multiple times. The expected format is ``name=value``. For example:: their name. Whitespace between different version specifiers is not allowed. If any one of the plugins is not found, emit an error. - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - [pytest] - required_plugins = pytest-django>=3.0.0,<4.0.0 pytest-html pytest-xdist>=1.0.0 + [pytest] + required_plugins = pytest-django>=3.0.0,<4.0.0 pytest-html pytest-xdist>=1.0.0 .. confval:: testpaths @@ -1966,10 +2025,12 @@ passed multiple times. The expected format is ``name=value``. For example:: Useful when all project tests are in a known location to speed up test collection and to avoid picking up undesired tests by accident. - .. code-block:: ini + .. tab:: ini - [pytest] - testpaths = testing doc + .. code-block:: ini + + [pytest] + testpaths = testing doc This configuration means that executing: @@ -1988,10 +2049,12 @@ passed multiple times. The expected format is ``name=value``. For example:: How many sessions should we keep the `tmp_path` directories, according to `tmp_path_retention_policy`. - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - [pytest] - tmp_path_retention_count = 3 + [pytest] + tmp_path_retention_count = 3 Default: ``3`` @@ -2007,10 +2070,12 @@ passed multiple times. The expected format is ``name=value``. For example:: * `failed`: retains directories only for tests with outcome `error` or `failed`. * `none`: directories are always removed after each test ends, regardless of the outcome. - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - [pytest] - tmp_path_retention_policy = all + [pytest] + tmp_path_retention_policy = all Default: ``all`` @@ -2021,10 +2086,12 @@ passed multiple times. The expected format is ``name=value``. For example:: Setting value to ``0`` disables the character limit for truncation. - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - [pytest] - truncation_limit_chars = 640 + [pytest] + truncation_limit_chars = 640 pytest truncates the assert messages to a certain limit by default to prevent comparison with large data to overload the console output. @@ -2041,10 +2108,12 @@ passed multiple times. The expected format is ``name=value``. For example:: Setting value to ``0`` disables the lines limit for truncation. - .. code-block:: ini + .. tab:: ini - [pytest] - truncation_limit_lines = 8 + .. code-block:: ini + + [pytest] + truncation_limit_lines = 8 pytest truncates the assert messages to a certain limit by default to prevent comparison with large data to overload the console output. @@ -2061,21 +2130,25 @@ passed multiple times. The expected format is ``name=value``. For example:: the ``@pytest.mark.usefixtures`` marker to all test functions. - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - [pytest] - usefixtures = - clean_db + [pytest] + usefixtures = + clean_db .. confval:: verbosity_assertions Set a verbosity level specifically for assertion related output, overriding the application wide level. - .. code-block:: ini + .. tab:: ini - [pytest] - verbosity_assertions = 2 + .. code-block:: ini + + [pytest] + verbosity_assertions = 2 Defaults to application wide verbosity level (via the ``-v`` command-line option). A special value of "auto" can be used to explicitly use the global verbosity level. @@ -2085,10 +2158,12 @@ passed multiple times. The expected format is ``name=value``. For example:: Set a verbosity level specifically for test case execution related output, overriding the application wide level. - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - [pytest] - verbosity_test_cases = 2 + [pytest] + verbosity_test_cases = 2 Defaults to application wide verbosity level (via the ``-v`` command-line option). A special value of "auto" can be used to explicitly use the global verbosity level. @@ -2096,7 +2171,7 @@ passed multiple times. The expected format is ``name=value``. For example:: .. confval:: strict - If set to ``True``, enables all strictness options: + If set to ``true``, enables all strictness options: * :confval:`strict_config` * :confval:`strict_markers` @@ -2112,25 +2187,28 @@ passed multiple times. The expected format is ``name=value``. For example:: We therefore only recommend using this option when using a locked version of pytest, or if you want to proactively adopt new strictness options as they are added. - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - [pytest] - strict = True + [pytest] + strict = true .. versionadded:: 9.0 .. confval:: strict_xfail - If set to ``True``, tests marked with ``@pytest.mark.xfail`` that actually succeed will by default fail the + If set to ``true``, tests marked with ``@pytest.mark.xfail`` that actually succeed will by default fail the test suite. For more information, see :ref:`xfail strict tutorial`. + .. tab:: ini - .. code-block:: ini + .. code-block:: ini - [pytest] - strict_xfail = True + [pytest] + strict_xfail = true You can also enable this option via the :confval:`strict` option. @@ -2141,39 +2219,44 @@ passed multiple times. The expected format is ``name=value``. For example:: .. confval:: strict_config - If set to ``True``, any warnings encountered while parsing the ``pytest`` section of the configuration file will raise errors. + If set to ``true``, any warnings encountered while parsing the ``pytest`` section of the configuration file will raise errors. - .. code-block:: ini + .. tab:: ini - [pytest] - strict_config = True + .. code-block:: ini + + [pytest] + strict_config = true You can also enable this option via the :confval:`strict` option. .. confval:: strict_markers - If set to ``True``, markers not registered in the ``markers`` section of the configuration file will raise errors. + If set to ``true``, markers not registered in the ``markers`` section of the configuration file will raise errors. - .. code-block:: ini + .. tab:: ini - [pytest] - strict_markers = True + .. code-block:: ini - You can also enable this option via the :confval:`strict` option. + [pytest] + strict_markers = true + You can also enable this option via the :confval:`strict` option. .. confval:: strict_parametrization_ids - If set, pytest emits an error if it detects non-unique parameter set IDs. + If set to ``true``, pytest emits an error if it detects non-unique parameter set IDs. If not set (the default), pytest automatically handles this by adding `0`, `1`, ... to duplicate IDs, making them unique. - .. code-block:: ini + .. tab:: ini - [pytest] - strict_parametrization_ids = True + .. code-block:: ini + + [pytest] + strict_parametrization_ids = true You can also enable this option via the :confval:`strict` option. diff --git a/doc/en/requirements.txt b/doc/en/requirements.txt index ddcb7efb99b..5483bb46063 100644 --- a/doc/en/requirements.txt +++ b/doc/en/requirements.txt @@ -8,3 +8,4 @@ sphinxcontrib-svg2pdfconverter furo sphinxcontrib-towncrier sphinx-issues +sphinx-inline-tabs diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index e6d34ffc5c4..6ce5223beaa 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -1,5 +1,5 @@ # mypy: allow-untyped-defs -"""Command line options, ini-file and conftest.py processing.""" +"""Command line options, config-file and conftest.py processing.""" from __future__ import annotations @@ -52,6 +52,7 @@ from _pytest._code import filter_traceback from _pytest._code.code import TracebackStyle from _pytest._io import TerminalWriter +from _pytest.compat import assert_never from _pytest.config.argparsing import Argument from _pytest.config.argparsing import Parser import _pytest.deprecated @@ -993,7 +994,7 @@ class InvocationParams: .. note:: Note that the environment variable ``PYTEST_ADDOPTS`` and the ``addopts`` - ini option are handled by pytest, not being included in the ``args`` attribute. + configuration option are handled by pytest, not being included in the ``args`` attribute. Plugins accessing ``InvocationParams`` must be aware of that. """ @@ -1451,8 +1452,8 @@ def _preparse(self, args: list[str], addopts: bool = True) -> None: @hookimpl(wrapper=True) def pytest_collection(self) -> Generator[None, object, object]: - # Validate invalid ini keys after collection is done so we take in account - # options added by late-loading conftest files. + # Validate invalid configuration keys after collection is done so we + # take in account options added by late-loading conftest files. try: return (yield) finally: @@ -1588,7 +1589,7 @@ def issue_config_time_warning(self, warning: Warning, stacklevel: int) -> None: ) def addinivalue_line(self, name: str, line: str) -> None: - """Add a line to an ini-file option. The option must have been + """Add a line to a configuration option. The option must have been declared but might not yet be set in which case the line becomes the first line in its value.""" x = self.getini(name) @@ -1596,11 +1597,11 @@ def addinivalue_line(self, name: str, line: str) -> None: x.append(line) # modifies the cached list inline def getini(self, name: str) -> Any: - """Return configuration value from an :ref:`ini file `. + """Return configuration value the an :ref:`configuration file `. - If a configuration value is not defined in an - :ref:`ini file `, then the ``default`` value provided while - registering the configuration through + If a configuration value is not defined in a + :ref:`configuration file `, then the ``default`` value + provided while registering the configuration through :func:`parser.addini ` will be returned. Please note that you can even provide ``None`` as a valid default value. @@ -1629,8 +1630,9 @@ def getini(self, name: str) -> Any: try: return self._inicache[canonical_name] except KeyError: - self._inicache[canonical_name] = val = self._getini(canonical_name) - return val + pass + self._inicache[canonical_name] = val = self._getini(canonical_name) + return val # Meant for easy monkeypatching by legacypath plugin. # Can be inlined back (with no cover removed) once legacypath is gone. @@ -1666,22 +1668,42 @@ def _getini(self, name: str): # 2. Canonical name takes precedence over alias. selected = max(candidates, key=lambda x: (x[0].origin == "override", x[1]))[0] value = selected.value + mode = selected.mode - # Coerce the values based on types. - # - # Note: some coercions are only required if we are reading from .ini files, because - # the file format doesn't contain type information, but when reading from toml we will - # get either str or list of str values (see _parse_ini_config_from_pyproject_toml). - # For example: + if mode == "ini": + # In ini mode, values are always str | list[str]. + assert isinstance(value, (str, list)) + return self._getini_ini(name, canonical_name, type, value, default) + else: + assert_never(mode) + + def _getini_ini( + self, + name: str, + canonical_name: str, + type: str, + value: str | list[str], + default: Any, + ): + """Handle config values read in INI mode. + + In INI mode, values are stored as str or list[str] only, and coerced + from string based on the registered type. + """ + # Note: some coercions are only required if we are reading from .ini + # files, because the file format doesn't contain type information, but + # when reading from toml (in ini mode) we will get either str or list of + # str values (see load_config_dict_from_file). For example: # # ini: # a_line_list = "tests acceptance" - # in this case, we need to split the string to obtain a list of strings. # - # toml: + # in this case, we need to split the string to obtain a list of strings. + # + # toml (ini mode): # a_line_list = ["tests", "acceptance"] - # in this case, we already have a list ready to use. # + # in this case, we already have a list ready to use. if type == "paths": dp = ( self.inipath.parent @@ -1790,11 +1812,12 @@ def get_verbosity(self, verbosity_type: str | None = None) -> int: Example: - .. code-block:: ini + .. tab:: ini + + .. code-block:: ini - # content of pytest.ini - [pytest] - verbosity_assertions = 2 + [pytest] + verbosity_assertions = 2 .. code-block:: console @@ -1828,7 +1851,7 @@ def _verbosity_ini_name(verbosity_type: str) -> str: def _add_verbosity_ini(parser: Parser, verbosity_type: str, help: str) -> None: """Add a output verbosity configuration option for the given output type. - :param parser: Parser for command line arguments and ini-file values. + :param parser: Parser for command line arguments and config-file values. :param verbosity_type: Fine-grained verbosity category. :param help: Description of the output this type controls. diff --git a/src/_pytest/config/argparsing.py b/src/_pytest/config/argparsing.py index 67b89bbbb16..99835884848 100644 --- a/src/_pytest/config/argparsing.py +++ b/src/_pytest/config/argparsing.py @@ -30,7 +30,7 @@ def __repr__(self) -> str: @final class Parser: - """Parser for command line arguments and ini-file values. + """Parser for command line arguments and config-file values. :ivar extra_info: Dict of generic param -> value to display in case there's an error processing the command line arguments. @@ -183,12 +183,12 @@ def addini( *, aliases: Sequence[str] = (), ) -> None: - """Register an ini-file option. + """Register a configuration file option. :param name: - Name of the ini-variable. + Name of the configuration. :param type: - Type of the variable. Can be: + Type of the configuration. Can be: * ``string``: a string * ``bool``: a boolean @@ -203,19 +203,19 @@ def addini( The ``float`` and ``int`` types. - For ``paths`` and ``pathlist`` types, they are considered relative to the ini-file. - In case the execution is happening without an ini-file defined, + For ``paths`` and ``pathlist`` types, they are considered relative to the config-file. + In case the execution is happening without a config-file defined, they will be considered relative to the current working directory (for example with ``--override-ini``). .. versionadded:: 7.0 The ``paths`` variable type. .. versionadded:: 8.1 - Use the current working directory to resolve ``paths`` and ``pathlist`` in the absence of an ini-file. + Use the current working directory to resolve ``paths`` and ``pathlist`` in the absence of a config-file. Defaults to ``string`` if ``None`` or not passed. :param default: - Default value if no ini-file option exists but is queried. + Default value if no config-file option exists but is queried. :param aliases: Additional names by which this option can be referenced. Aliases resolve to the canonical name. @@ -223,7 +223,7 @@ def addini( .. versionadded:: 9.0 The ``aliases`` parameter. - The value of ini-variables can be retrieved via a call to + The value of configuration keys can be retrieved via a call to :py:func:`config.getini(name) `. """ assert type in ( @@ -246,7 +246,9 @@ def addini( for alias in aliases: if alias in self._inidict: - raise ValueError(f"alias {alias!r} conflicts with existing ini option") + raise ValueError( + f"alias {alias!r} conflicts with existing configuration option" + ) if (already := self._ini_aliases.get(alias)) is not None: raise ValueError(f"{alias!r} is already an alias of {already!r}") self._ini_aliases[alias] = name @@ -258,7 +260,7 @@ def get_ini_default_for_type( ], ) -> Any: """ - Used by addini to get the default value for a given ini-option type, when + Used by addini to get the default value for a given config option type, when default is not supplied. """ if type in ("paths", "pathlist", "args", "linelist"): @@ -553,7 +555,7 @@ class OverrideIniAction(argparse.Action): """Custom argparse action that makes a CLI flag equivalent to overriding an option, in addition to behaving like `store_true`. - This can simplify things since code only needs to inspect the ini option + This can simplify things since code only needs to inspect the config option and not consider the CLI flag. """ diff --git a/src/_pytest/config/findpaths.py b/src/_pytest/config/findpaths.py index 5b190d3174b..6fa900f0e9b 100644 --- a/src/_pytest/config/findpaths.py +++ b/src/_pytest/config/findpaths.py @@ -21,19 +21,21 @@ @dataclass(frozen=True) class ConfigValue: - """Represents a configuration value with its origin. + """Represents a configuration value with its origin and parsing mode. This allows tracking whether a value came from a configuration file or from a CLI override (--override-ini), which is important for determining precedence when dealing with ini option aliases. + + The mode tracks the parsing mode/data model used for the value: + - "ini": from INI files or [tool.pytest.ini_options], where the only + supported value types are `str` or `list[str]`. """ - # Even though TOML supports richer data types, all values are converted to - # str/list[str] during parsing to maintain compatibility with the rest of - # the configuration system. - value: str | list[str] + value: object _: KW_ONLY origin: Literal["file", "override"] + mode: Literal["ini"] ConfigDict: TypeAlias = dict[str, ConfigValue] @@ -64,7 +66,8 @@ def load_config_dict_from_file( if "pytest" in iniconfig: return { - k: ConfigValue(v, origin="file") for k, v in iniconfig["pytest"].items() + k: ConfigValue(v, origin="file", mode="ini") + for k, v in iniconfig["pytest"].items() } else: # "pytest.ini" files are always the source of configuration, even if empty. @@ -77,7 +80,7 @@ def load_config_dict_from_file( if "tool:pytest" in iniconfig.sections: return { - k: ConfigValue(v, origin="file") + k: ConfigValue(v, origin="file", mode="ini") for k, v in iniconfig["tool:pytest"].items() } elif "pytest" in iniconfig.sections: @@ -107,7 +110,8 @@ def make_scalar(v: object) -> str | list[str]: return v if isinstance(v, list) else str(v) return { - k: ConfigValue(make_scalar(v), origin="file") for k, v in result.items() + k: ConfigValue(make_scalar(v), origin="file", mode="ini") + for k, v in result.items() } return None @@ -224,7 +228,7 @@ def parse_override_ini(override_ini: Sequence[str] | None) -> ConfigDict: f"-o/--override-ini expects option=value style (got: {ini_config!r})." ) from e else: - overrides[key] = ConfigValue(user_ini_value, origin="override") + overrides[key] = ConfigValue(user_ini_value, origin="override", mode="ini") return overrides diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index b3597615ba9..27846db13a4 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1520,7 +1520,7 @@ class FixtureManager: relevant for a particular function. An initial list of fixtures is assembled like this: - - ini-defined usefixtures + - config-defined usefixtures - autouse-marked fixtures along the collection chain up from the function - usefixtures markers at module/class/function level - test function funcargs diff --git a/src/_pytest/helpconfig.py b/src/_pytest/helpconfig.py index 32b807029fb..1b8c885bcdb 100644 --- a/src/_pytest/helpconfig.py +++ b/src/_pytest/helpconfig.py @@ -102,7 +102,7 @@ def pytest_addoption(parser: Parser) -> None: "--override-ini", dest="override_ini", action="append", - help='Override ini option with "option=value" style, ' + help='Override configuration option with "option=value" style, ' "e.g. `-o strict_xfail=True -o cache_dir=cache`.", ) @@ -176,7 +176,7 @@ def showhelp(config: Config) -> None: tw.write(config._parser.optparser.format_help()) tw.line() tw.line( - "[pytest] ini-options in the first " + "[pytest] configuration options in the first " "pytest.ini|tox.ini|setup.cfg|pyproject.toml file found:" ) tw.line() diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index 12653ea11fe..29ad272af23 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -98,13 +98,13 @@ def pytest_plugin_registered( @hookspec(historic=True) def pytest_addoption(parser: Parser, pluginmanager: PytestPluginManager) -> None: - """Register argparse-style options and ini-style config values, + """Register argparse-style options and config-style config values, called once at the beginning of a test run. :param parser: To add command line options, call :py:func:`parser.addoption(...) `. - To add ini-file values call :py:func:`parser.addini(...) + To add config-file values call :py:func:`parser.addini(...) `. :param pluginmanager: @@ -119,7 +119,7 @@ def pytest_addoption(parser: Parser, pluginmanager: PytestPluginManager) -> None retrieve the value of a command line option. - :py:func:`config.getini(name) ` to retrieve - a value read from an ini-style file. + a value read from a configuration file. The config object is passed around on many internal objects via the ``.config`` attribute or can be retrieved as the ``pytestconfig`` fixture. @@ -998,13 +998,15 @@ def pytest_assertion_pass(item: Item, lineno: int, orig: str, expl: str) -> None and the pytest introspected assertion information is available in the `expl` string. - This hook must be explicitly enabled by the ``enable_assertion_pass_hook`` - ini-file option: + This hook must be explicitly enabled by the :confval:`enable_assertion_pass_hook` + configuration option: - .. code-block:: ini + .. tab:: ini - [pytest] - enable_assertion_pass_hook=true + .. code-block:: ini + + [pytest] + enable_assertion_pass_hook = true You need to **clean the .pyc** files in your project directory and interpreter libraries when enabling this option, as assertions will require to be re-written. diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 67b1c0474f7..e63751877a4 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -383,7 +383,7 @@ def istestclass(self, obj: object, name: str) -> bool: def _matches_prefix_or_glob_option(self, option_name: str, name: str) -> bool: """Check if the given name matches the prefix or glob-pattern defined - in ini configuration.""" + in configuration.""" for option in self.config.getini(option_name): if name.startswith(option): return True diff --git a/testing/logging/test_reporting.py b/testing/logging/test_reporting.py index cf54788e246..4974532e888 100644 --- a/testing/logging/test_reporting.py +++ b/testing/logging/test_reporting.py @@ -589,7 +589,8 @@ def test_log_cli(request): ) def test_log_cli_auto_enable(pytester: Pytester, cli_args: str) -> None: """Check that live logs are enabled if --log-level or --log-cli-level is passed on the CLI. - It should not be auto enabled if the same configs are set on the INI file. + + It should not be auto enabled if the same configs are set on the configuration file. """ pytester.makepyfile( """ diff --git a/testing/test_collection.py b/testing/test_collection.py index cd8e13c8790..39753d80cac 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -1353,7 +1353,7 @@ def test_collect_pyargs_with_testpaths( def test_initial_conftests_with_testpaths(pytester: Pytester) -> None: - """The testpaths ini option should load conftests in those paths as 'initial' (#10987).""" + """The testpaths config option should load conftests in those paths as 'initial' (#10987).""" p = pytester.mkdir("some_path") p.joinpath("conftest.py").write_text( textwrap.dedent( diff --git a/testing/test_config.py b/testing/test_config.py index 34fab5272e8..dd42c80992f 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -58,9 +58,9 @@ def test_getcfg_and_config( encoding="utf-8", ) _, _, cfg, _ = locate_config(Path.cwd(), [sub]) - assert cfg["name"] == ConfigValue("value", origin="file") + assert cfg["name"] == ConfigValue("value", origin="file", mode="ini") config = pytester.parseconfigure(str(sub)) - assert config.inicfg["name"] == ConfigValue("value", origin="file") + assert config.inicfg["name"] == ConfigValue("value", origin="file", mode="ini") def test_setupcfg_uses_toolpytest_with_pytest(self, pytester: Pytester) -> None: p1 = pytester.makepyfile("def test(): pass") @@ -1043,7 +1043,7 @@ def pytest_addoption(parser): assert config.getini("old_name") == "hello" def test_addini_aliases_with_canonical_in_file(self, pytester: Pytester) -> None: - """Test that canonical name takes precedence over alias in ini file.""" + """Test that canonical name takes precedence over alias in configuration file.""" pytester.makeconftest( """ def pytest_addoption(parser): @@ -1160,7 +1160,7 @@ def pytest_addoption(parser): try: parser.addini("new_option", "second option", aliases=["existing"]) except ValueError as e: - assert "alias 'existing' conflicts with existing ini option" in str(e) + assert "alias 'existing' conflicts with existing configuration option" in str(e) else: assert False, "Should have raised ValueError" """ @@ -1330,7 +1330,9 @@ def test_inifilename(self, tmp_path: Path) -> None: # this indicates this is the file used for getting configuration values assert config.inipath == inipath - assert config.inicfg.get("name") == ConfigValue("value", origin="file") + assert config.inicfg.get("name") == ConfigValue( + "value", origin="file", mode="ini" + ) assert config.inicfg.get("should_not_be_set") is None @@ -1824,7 +1826,7 @@ def test_with_ini(self, tmp_path: Path, name: str, contents: str) -> None: ) assert rootpath == tmp_path assert parsed_inipath == inipath - assert ini_config["x"] == ConfigValue("10", origin="file") + assert ini_config["x"] == ConfigValue("10", origin="file", mode="ini") @pytest.mark.parametrize("pytest_ini", ["pytest.ini", ".pytest.ini"]) @pytest.mark.parametrize("other", ["setup.cfg", "tox.ini"]) @@ -1901,7 +1903,7 @@ def test_with_specific_inifile( ) assert rootpath == tmp_path assert inipath == p - assert ini_config["x"] == ConfigValue("10", origin="file") + assert ini_config["x"] == ConfigValue("10", origin="file", mode="ini") def test_explicit_config_file_sets_rootdir( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch @@ -2147,7 +2149,7 @@ def test_override_ini_usage_error_bad_style(self, pytester: Pytester) -> None: def test_override_ini_handled_asap( self, pytester: Pytester, with_ini: bool ) -> None: - """-o should be handled as soon as possible and always override what's in ini files (#2238)""" + """-o should be handled as soon as possible and always override what's in config files (#2238)""" if with_ini: pytester.makeini( """ @@ -2172,7 +2174,7 @@ def test_addopts_before_initini( config = _config_for_test config._preparse([], addopts=True) assert config.inicfg.get("cache_dir") == ConfigValue( - cache_dir, origin="override" + cache_dir, origin="override", mode="ini" ) def test_addopts_from_env_not_concatenated( @@ -2189,7 +2191,7 @@ def test_addopts_from_env_not_concatenated( ) def test_addopts_from_ini_not_concatenated(self, pytester: Pytester) -> None: - """`addopts` from ini should not take values from normal args (#4265).""" + """`addopts` from configuration should not take values from normal args (#4265).""" pytester.makeini( """ [pytest] @@ -2212,7 +2214,7 @@ def test_override_ini_does_not_contain_paths( config = _config_for_test config._preparse(["-o", "cache_dir=/cache", "/some/test/path"]) assert config.inicfg.get("cache_dir") == ConfigValue( - "/cache", origin="override" + "/cache", origin="override", mode="ini" ) def test_multiple_override_ini_options(self, pytester: Pytester) -> None: diff --git a/testing/test_conftest.py b/testing/test_conftest.py index bd083574ffc..4de61bceb90 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -654,7 +654,7 @@ def test_parsefactories_relative_node_ids( def test_search_conftest_up_to_inifile( pytester: Pytester, confcutdir: str, passed: int, error: int ) -> None: - """Test that conftest files are detected only up to an ini file, unless + """Test that conftest files are detected only up to a configuration file, unless an explicit --confcutdir option is given. """ root = pytester.path diff --git a/testing/test_doctest.py b/testing/test_doctest.py index e2ca1119e92..8b71dabbc77 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -907,7 +907,7 @@ class TestLiterals: def test_allow_unicode(self, pytester, config_mode): """Test that doctests which output unicode work in all python versions tested by pytest when the ALLOW_UNICODE option is used (either in - the ini file or by an inline comment). + the configuration file or by an inline comment). """ if config_mode == "ini": pytester.makeini( @@ -942,7 +942,7 @@ def foo(): def test_allow_bytes(self, pytester, config_mode): """Test that doctests which output bytes work in all python versions tested by pytest when the ALLOW_BYTES option is used (either in - the ini file or by an inline comment)(#1287). + the configuration file or by an inline comment)(#1287). """ if config_mode == "ini": pytester.makeini( diff --git a/testing/test_findpaths.py b/testing/test_findpaths.py index 0ed53a02418..674ae79b4fe 100644 --- a/testing/test_findpaths.py +++ b/testing/test_findpaths.py @@ -26,13 +26,17 @@ def test_pytest_ini(self, tmp_path: Path) -> None: """[pytest] section in pytest.ini files is read correctly""" fn = tmp_path / "pytest.ini" fn.write_text("[pytest]\nx=1", encoding="utf-8") - assert load_config_dict_from_file(fn) == {"x": ConfigValue("1", origin="file")} + assert load_config_dict_from_file(fn) == { + "x": ConfigValue("1", origin="file", mode="ini") + } def test_custom_ini(self, tmp_path: Path) -> None: """[pytest] section in any .ini file is read correctly""" fn = tmp_path / "custom.ini" fn.write_text("[pytest]\nx=1", encoding="utf-8") - assert load_config_dict_from_file(fn) == {"x": ConfigValue("1", origin="file")} + assert load_config_dict_from_file(fn) == { + "x": ConfigValue("1", origin="file", mode="ini") + } def test_custom_ini_without_section(self, tmp_path: Path) -> None: """Custom .ini files without [pytest] section are not considered for configuration""" @@ -50,7 +54,9 @@ def test_valid_cfg_file(self, tmp_path: Path) -> None: """Custom .cfg files with [tool:pytest] section are read correctly""" fn = tmp_path / "custom.cfg" fn.write_text("[tool:pytest]\nx=1", encoding="utf-8") - assert load_config_dict_from_file(fn) == {"x": ConfigValue("1", origin="file")} + assert load_config_dict_from_file(fn) == { + "x": ConfigValue("1", origin="file", mode="ini") + } def test_unsupported_pytest_section_in_cfg_file(self, tmp_path: Path) -> None: """.cfg files with [pytest] section are no longer supported and should fail to alert users""" @@ -98,11 +104,11 @@ def test_valid_toml_file(self, tmp_path: Path) -> None: encoding="utf-8", ) assert load_config_dict_from_file(fn) == { - "x": ConfigValue("1", origin="file"), - "y": ConfigValue("20.0", origin="file"), - "values": ConfigValue(["tests", "integration"], origin="file"), - "name": ConfigValue("foo", origin="file"), - "heterogeneous_array": ConfigValue([1, "str"], origin="file"), # type: ignore[list-item] + "x": ConfigValue("1", origin="file", mode="ini"), + "y": ConfigValue("20.0", origin="file", mode="ini"), + "values": ConfigValue(["tests", "integration"], origin="file", mode="ini"), + "name": ConfigValue("foo", origin="file", mode="ini"), + "heterogeneous_array": ConfigValue([1, "str"], origin="file", mode="ini"), } diff --git a/testing/test_python_path.py b/testing/test_python_path.py index d12ef96115f..f75bcb6bb57 100644 --- a/testing/test_python_path.py +++ b/testing/test_python_path.py @@ -92,8 +92,8 @@ def test_module_not_found(pytester: Pytester, file_structure) -> None: result.stdout.fnmatch_lines([expected_error]) -def test_no_ini(pytester: Pytester, file_structure) -> None: - """If no ini file, test should error.""" +def test_no_config_file(pytester: Pytester, file_structure) -> None: + """If no configuration file, test should error.""" result = pytester.runpytest("test_foo.py") assert result.ret == pytest.ExitCode.INTERRUPTED result.assert_outcomes(errors=1) diff --git a/testing/test_terminal.py b/testing/test_terminal.py index e6b77ae5546..cfc668fa6ad 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -940,7 +940,7 @@ def test_header(self, pytester: Pytester) -> None: pytester.path.joinpath("tests").mkdir() pytester.path.joinpath("gui").mkdir() - # no ini file + # no configuration file result = pytester.runpytest() result.stdout.fnmatch_lines(["rootdir: *test_header0"]) diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 4800a916eac..d13ed72a2d4 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -382,7 +382,7 @@ def test_bar(): def test_option_precedence_cmdline_over_ini( pytester: Pytester, ignore_on_cmdline ) -> None: - """Filters defined in the command-line should take precedence over filters in ini files (#3946).""" + """Filters defined in the command-line should take precedence over filters in config files (#3946).""" pytester.makeini( """ [pytest] From 7672dafb2a4dc0d9d9d0ae07a05c0b74ce57bd96 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 24 Oct 2025 22:48:47 +0300 Subject: [PATCH 4/5] config: add native TOML configuration Add support for using native TOML configuration, while maintaining full backwards compatibility with the existing INI-based configuration system. In pyproject.toml, the native configuration is under ``[pytest.tool]``. Also add support for ``pytest.toml``/``.pytest.toml`` files. `--override-ini` always uses "ini" mode for compatibility. --- changelog/13743.feature.rst | 37 +++ doc/en/deprecations.rst | 14 ++ doc/en/example/markers.rst | 7 + doc/en/example/pythoncollection.rst | 39 +++ doc/en/example/simple.rst | 7 + doc/en/explanation/goodpractices.rst | 14 ++ doc/en/how-to/capture-warnings.rst | 28 +++ doc/en/how-to/doctest.rst | 21 ++ doc/en/how-to/fixtures.rst | 7 + doc/en/how-to/logging.rst | 23 ++ doc/en/how-to/mark.rst | 21 ++ doc/en/how-to/output.rst | 22 ++ doc/en/how-to/parametrize.rst | 7 + doc/en/how-to/plugins.rst | 14 ++ doc/en/how-to/skipping.rst | 7 + doc/en/how-to/writing_plugins.rst | 7 + doc/en/reference/customize.rst | 60 ++++- doc/en/reference/reference.rst | 340 ++++++++++++++++++++++++++- src/_pytest/config/__init__.py | 94 ++++++++ src/_pytest/config/findpaths.py | 68 ++++-- src/_pytest/helpconfig.py | 2 +- src/_pytest/hookspec.py | 7 + src/_pytest/pytester.py | 10 + testing/test_config.py | 274 ++++++++++++++++++++- testing/test_findpaths.py | 59 ++++- testing/test_legacypath.py | 16 +- 26 files changed, 1170 insertions(+), 35 deletions(-) create mode 100644 changelog/13743.feature.rst diff --git a/changelog/13743.feature.rst b/changelog/13743.feature.rst new file mode 100644 index 00000000000..b36782487f5 --- /dev/null +++ b/changelog/13743.feature.rst @@ -0,0 +1,37 @@ +Added support for native TOML configuration files. + +While pytest, since version 6, supports configuration in ``pyproject.toml`` files under ``[tool.pytest.ini_options]``, +it does so in an "INI compatibility mode", where all configuration values are treated as strings or list of strings. +Now, pytest supports the native TOML data model. + +In ``pyproject.toml``, the native TOML configuration is under the ``[tool.pytest]`` table. + +.. code-block:: toml + + # pyproject.toml + [tool.pytest] + minversion = "9.0" + addopts = ["-ra", "-q"] + testpaths = [ + "tests", + "integration", + ] + +The ``[tool.pytest.ini_options]`` table remains supported, but both tables cannot be used at the same time. + +If you prefer to use a separate configuration file, or don't use ``pyproject.toml``, you can use ``pytest.toml`` or ``.pytest.toml``: + +.. code-block:: toml + + # pytest.toml or .pytest.toml + [pytest] + minversion = "9.0" + addopts = ["-ra", "-q"] + testpaths = [ + "tests", + "integration", + ] + +The documentation now shows configuration snippets in both TOML and INI formats, in a tabbed interface. + +See :ref:`config file formats` for full details. diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index af8bc4108a6..65a05823517 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -847,6 +847,13 @@ XML file supports it. To use the new format, update your configuration file: +.. tab:: toml + + .. code-block:: toml + + [pytest] + junit_family = "xunit2" + .. tab:: ini .. code-block:: ini @@ -857,6 +864,13 @@ To use the new format, update your configuration file: If you discover that your tooling does not support the new format, and want to keep using the legacy version, set the option to ``legacy`` instead: +.. tab:: toml + + .. code-block:: toml + + [pytest] + junit_family = "legacy" + .. tab:: ini .. code-block:: ini diff --git a/doc/en/example/markers.rst b/doc/en/example/markers.rst index 944047c583d..ed830b9fb2e 100644 --- a/doc/en/example/markers.rst +++ b/doc/en/example/markers.rst @@ -239,6 +239,13 @@ Registering markers Registering markers for your test suite is simple: +.. tab:: toml + + .. code-block:: toml + + [pytest] + markers = ["webtest: mark a test as a webtest.", "slow: mark test as slow."] + .. tab:: ini .. code-block:: ini diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index e0020384864..5ff3ae20247 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -90,6 +90,13 @@ Changing directory recursion You can set the :confval:`norecursedirs` option in a configuration file: +.. tab:: toml + + .. code-block:: toml + + [pytest] + norecursedirs = [".svn", "_build", "tmp*"] + .. tab:: ini .. code-block:: ini @@ -109,6 +116,16 @@ the :confval:`python_files`, :confval:`python_classes` and :confval:`python_functions` in your :ref:`configuration file `. Here is an example: +.. tab:: toml + + .. code-block:: toml + + # Example 1: have pytest look for "check" instead of "test" + [pytest] + python_files = ["check_*.py"] + python_classes = ["Check"] + python_functions = ["*_check"] + .. tab:: ini .. code-block:: ini @@ -154,6 +171,14 @@ The test collection would look like this: You can check for multiple glob patterns by adding a space between the patterns: +.. tab:: toml + + .. code-block:: toml + + # Example 2: have pytest look for files with "test" and "example" + [pytest] + python_files = ["test_*.py", "example_*.py"] + .. tab:: ini .. code-block:: ini @@ -184,6 +209,13 @@ which would run the respective test module. Like with other options, through a configuration file and the :confval:`addopts` option you can make this change more permanently: +.. tab:: toml + + .. code-block:: toml + + [pytest] + addopts = ["--pyargs"] + .. tab:: ini .. code-block:: ini @@ -228,6 +260,13 @@ Customizing test collection You can easily instruct ``pytest`` to discover tests from every Python file: +.. tab:: toml + + .. code-block:: toml + + [pytest] + python_files = ["*.py"] + .. tab:: ini .. code-block:: ini diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index 7c1822efae5..7effb480480 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -11,6 +11,13 @@ every time you use ``pytest``. For example, if you always want to see detailed info on skipped and xfailed tests, as well as have terser "dot" progress output, you can write it into a configuration file: +.. tab:: toml + + .. code-block:: toml + + [pytest] + addopts = ["-ra", "-q"] + .. tab:: ini .. code-block:: ini diff --git a/doc/en/explanation/goodpractices.rst b/doc/en/explanation/goodpractices.rst index 70a2e1d3872..83c6a5f4b56 100644 --- a/doc/en/explanation/goodpractices.rst +++ b/doc/en/explanation/goodpractices.rst @@ -96,6 +96,13 @@ For new projects, we recommend to use ``importlib`` :ref:`import mode