Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions changelog/13823.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Added a :confval:`strict` configuration option to enable all strictness-related options.

When set to ``True``, the :confval:`strict` option currently enables :confval:`strict_config`,
:confval:`strict_markers`, :confval:`strict_xfail`, and :confval:`strict_parametrization_ids`.

The individual strictness options can be explicitly set to override the global :confval:`strict` setting.

If new strictness options are added in the future, they will also be automatically enabled by :confval:`strict`.
Therefore, we only recommend setting ``strict=True`` if you're using a locked version of pytest,
or if you want to proactively adopt new strictness options as they are added.
7 changes: 7 additions & 0 deletions changelog/13823.improvement.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Added :confval:`strict_xfail` as an alias to the ``xfail_strict`` option,
:confval:`strict_config` as an alias to the ``--strict-config`` flag,
and :confval:`strict_markers` as an alias to the ``--strict-markers`` flag.
This makes all strictness options consistently have configuration options with the prefix ``strict_``.

Added :meth:`Config.hasini() <pytest.Config.hasini>` method to check whether a configuration option has been explicitly set (via configuration file or ``--override-ini``),
as opposed to just using its default value.
16 changes: 9 additions & 7 deletions doc/en/deprecations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -589,18 +589,20 @@ removed in pytest 8 (deprecated since pytest 2.4.0):
- ``parser.addoption(..., type="int/string/float/complex")`` - use ``type=int`` etc. instead.


The ``--strict`` command-line option
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``--strict`` command-line option (reintroduced)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. deprecated:: 6.2
.. versionremoved:: 8.0
.. versionchanged:: 9.0

The ``--strict`` command-line option has been deprecated in favor of ``--strict-markers``, which
The ``--strict`` command-line option had been deprecated in favor of ``--strict-markers``, which
better conveys what the option does.

We have plans to maybe in the future to reintroduce ``--strict`` and make it an encompassing
flag for all strictness related options (``--strict-markers`` and ``--strict-config``
at the moment, more might be introduced in the future).
In version 8.1, we accidentally un-deprecated ``--strict``.

In version 9.0, we changed ``--strict`` to make it set the new :confval:`strict`
configuration option. It now enables all strictness related options (including
:confval:`strict_markers`).


.. _cmdline-preparse-deprecated:
Expand Down
3 changes: 1 addition & 2 deletions doc/en/example/markers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,7 @@ For an example on how to add and work with markers from a plugin, see

* Asking for existing markers via ``pytest --markers`` gives good output

* Typos in function markers are treated as an error if you use
the ``--strict-markers`` option.
* Typos in function markers are treated as an error if you use the :confval:`strict_markers` configuration option.

.. _`scoped-marking`:

Expand Down
6 changes: 3 additions & 3 deletions doc/en/how-to/mark.rst
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,14 @@ surprising due to mistyped names. As described in the previous section, you can
the warning for custom marks by registering them in your ``pytest.ini`` file or
using a custom ``pytest_configure`` hook.

When the ``--strict-markers`` command-line flag is passed, any unknown marks applied
When the :confval:`strict_markers` ini 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 adding ``--strict-markers`` to ``addopts``:
enforce this validation in your project by setting :confval:`strict_markers` in your configuration:

.. code-block:: ini

[pytest]
addopts = --strict-markers
strict_markers = True
markers =
slow: marks tests as slow (deselect with '-m "not slow"')
serial
4 changes: 2 additions & 2 deletions doc/en/how-to/skipping.rst
Original file line number Diff line number Diff line change
Expand Up @@ -331,12 +331,12 @@ You can change this by setting the ``strict`` keyword-only parameter to ``True``
This will make ``XPASS`` ("unexpectedly passing") results from this test to fail the test suite.

You can change the default value of the ``strict`` parameter using the
``xfail_strict`` ini option:
``strict_xfail`` ini option:

.. code-block:: ini

[pytest]
xfail_strict=true
strict_xfail=true


Ignoring xfail
Expand Down
77 changes: 66 additions & 11 deletions doc/en/reference/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ pytest.mark.xfail

Marks a test function as *expected to fail*.

.. py:function:: pytest.mark.xfail(condition=False, *, reason=None, raises=None, run=True, strict=xfail_strict)
.. py:function:: pytest.mark.xfail(condition=False, *, reason=None, raises=None, run=True, strict=strict_xfail)

:keyword Union[bool, str] condition:
Condition for marking the test function as xfail (``True/False`` or a
Expand All @@ -286,7 +286,7 @@ Marks a test function as *expected to fail*.
that are always failing and there should be a clear indication if they unexpectedly start to pass (for example
a new release of a library fixes a known bug).

Defaults to :confval:`xfail_strict`, which is ``False`` by default.
Defaults to :confval:`strict_xfail`, which is ``False`` by default.


Custom marks
Expand Down Expand Up @@ -1774,25 +1774,21 @@ passed multiple times. The expected format is ``name=value``. For example::

.. confval:: markers

When the ``--strict-markers`` or ``--strict`` command-line arguments are used,
When the :confval:`strict_markers` configuration option is set,
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 add ``--strict-markers`` to ``addopts``
in which case you probably want to set :confval:`strict_markers` to ``True``
to avoid future regressions:

.. code-block:: ini

[pytest]
addopts = --strict-markers
strict_markers = True
markers =
slow
serial

.. note::
The use of ``--strict-markers`` is highly preferred. ``--strict`` was kept for
backward compatibility only and may be confusing for others as it only applies to
markers and not to other options.

.. confval:: minversion

Expand Down Expand Up @@ -2070,7 +2066,33 @@ passed multiple times. The expected format is ``name=value``. For example::
"auto" can be used to explicitly use the global verbosity level.


.. confval:: xfail_strict
.. confval:: strict

If set to ``True``, enables all strictness options:

* :confval:`strict_config`
* :confval:`strict_markers`
* :confval:`strict_xfail`
* :confval:`strict_parametrization_ids`

Plugins may also enable their own strictness options.

If you explicitly set an individual strictness option, it takes precedence over ``strict``.

.. note::
If new strictness options are added to pytest in the future, they will also be enabled by ``strict``.
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

[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
test suite.
Expand All @@ -2080,7 +2102,38 @@ passed multiple times. The expected format is ``name=value``. For example::
.. code-block:: ini

[pytest]
xfail_strict = True
strict_xfail = True

You can also enable this option via the :confval:`strict` option.

.. versionchanged:: 9.0
Renamed from ``xfail_strict`` to ``strict_xfail``.
``xfail_strict`` is accepted as an alias for ``strict_xfail``.


.. confval:: strict_config

If set to ``True``, any warnings encountered while parsing the ``pytest`` section of the configuration file will raise errors.

.. 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.

.. code-block:: ini

[pytest]
strict_markers = True

You can also enable this option via the :confval:`strict` option.


.. confval:: strict_parametrization_ids

Expand All @@ -2094,6 +2147,8 @@ passed multiple times. The expected format is ``name=value``. For example::
[pytest]
strict_parametrization_ids = True

You can also enable this option via the :confval:`strict` option.

For example,

.. code-block:: python
Expand Down
5 changes: 2 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ max_supported_python = "3.14"

[tool.pytest.ini_options]
minversion = "2.0"
addopts = "-rfEX -p pytester --strict-markers"
addopts = "-rfEX -p pytester"
python_files = [
"test_*.py",
"*_test.py",
Expand All @@ -378,8 +378,7 @@ norecursedirs = [
"build",
"dist",
]
xfail_strict = true
strict_parametrization_ids = true
strict = true
filterwarnings = [
"error",
"default:Using or importing the ABCs:DeprecationWarning:unittest2.*",
Expand Down
37 changes: 36 additions & 1 deletion src/_pytest/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1515,7 +1515,12 @@ def _validate_plugins(self) -> None:
)

def _warn_or_fail_if_strict(self, message: str) -> None:
if self.known_args_namespace.strict_config:
if self.hasini("strict_config"):
strict_config = self.getini("strict_config")
else:
strict_config = self.getini("strict")

if strict_config:
raise UsageError(message)

self.issue_config_time_warning(PytestConfigWarning(message), stacklevel=3)
Expand Down Expand Up @@ -1629,6 +1634,36 @@ def getini(self, name: str) -> Any:
self._inicache[canonical_name] = val = self._getini(canonical_name)
return val

def hasini(self, name: str) -> bool:
"""Return whether the configuration value was explicitly defined.

Returns ``True`` if the configuration option was explicitly set
either in an :ref:`ini file <configfiles>` or via command-line
override (``--override-ini``). Returns ``False`` if only the
default value would be used.

This can be used to distinguish between a value being explicitly set
to its default versus not being set at all.

If the specified name hasn't been registered through a prior
:func:`parser.addini <pytest.Parser.addini>` call (usually from a
plugin), a ValueError is raised.

.. versionadded:: 9.0
"""
canonical_name = self._parser._ini_aliases.get(name, name)

if canonical_name not in self._parser._inidict:
raise ValueError(f"unknown configuration value: {name!r}")

if canonical_name in self.inicfg:
return True
for alias, target in self._parser._ini_aliases.items():
if target == canonical_name and alias in self.inicfg:
return True

return False

# Meant for easy monkeypatching by legacypath plugin.
# Can be inlined back (with no cover removed) once legacypath is gone.
def _getini_unknown_type(self, name: str, type: str, value: object):
Expand Down
37 changes: 37 additions & 0 deletions src/_pytest/config/argparsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,3 +550,40 @@ def _split_lines(self, text, width):
for line in text.splitlines():
lines.extend(textwrap.wrap(line.strip(), width))
return lines


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
and not consider the CLI flag.
"""

def __init__(
self,
option_strings: Sequence[str],
dest: str,
nargs: int | str | None = None,
*args,
ini_option: str,
ini_value: str,
**kwargs,
) -> None:
super().__init__(option_strings, dest, 0, *args, **kwargs)
self.ini_option = ini_option
self.ini_value = ini_value

def __call__(
self,
parser: argparse.ArgumentParser,
namespace: argparse.Namespace,
*args,
**kwargs,
) -> None:
setattr(namespace, self.dest, True)
current_overrides = getattr(namespace, "override_ini", None)
if current_overrides is None:
current_overrides = []
Comment on lines +585 to +587
Copy link
Member

Choose a reason for hiding this comment

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

This can be simplifed:

Suggested change
current_overrides = getattr(namespace, "override_ini", None)
if current_overrides is None:
current_overrides = []
current_overrides = getattr(namespace, "override_ini", [])

Copy link
Member Author

Choose a reason for hiding this comment

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

It's written this way because namespace.override_ini can be set to None. I think it starts as None.

current_overrides.append(f"{self.ini_option}={self.ini_value}")
setattr(namespace, "override_ini", current_overrides)
2 changes: 1 addition & 1 deletion src/_pytest/helpconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def pytest_addoption(parser: Parser) -> None:
dest="override_ini",
action="append",
help='Override ini option with "option=value" style, '
"e.g. `-o xfail_strict=True -o cache_dir=cache`.",
"e.g. `-o strict_xfail=True -o cache_dir=cache`.",
)


Expand Down
Loading