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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/6881.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added a ``parametrize_long_str_id_strategy`` ini option to control how long ``str``/``bytes`` parameter values are handled in auto-generated test IDs. Explicit IDs set via ``ids=[...]`` or ``pytest.param(..., id=...)`` are not affected. Available strategies: ``short`` (default, values over 100 chars fall back to ``argname+index``), ``sha256`` (SHA-256 hex digest), ``legacy`` (keep full value), and ``disallow`` (error requesting explicit IDs).
48 changes: 48 additions & 0 deletions doc/en/reference/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1494,6 +1494,54 @@ passed multiple times. The expected format is ``name=value``. For example::

See :ref:`parametrizemark`.


.. confval:: parametrize_long_str_id_strategy
:type: ``str``
:default: ``"short"``

.. versionadded:: 9.1

Strategy for handling long ``str`` or ``bytes`` parameter values when
auto-generating test IDs for ``@pytest.mark.parametrize``. This only
affects auto-generated IDs — explicit IDs set via ``ids=[...]`` or
``pytest.param(..., id=...)`` are never affected.

Available strategies:

``short``
Values over 100 characters are replaced with ``<argname><index>``
(e.g. ``a0``, ``a1``). This is the default.

``sha256``
Replace the value with its SHA-256 hex digest, producing a
fixed-length, content-based ID.

``legacy``
Keep the full value as the ID regardless of length. Use this for
temporary backward compatibility during migration.

``disallow``
Raise an error for values over 100 characters, requiring the user
to set explicit IDs.

Example configuration:

.. tab:: toml

.. code-block:: toml

[pytest]
parametrize_long_str_id_strategy = "sha256"

.. tab:: ini

.. code-block:: ini

[pytest]
parametrize_long_str_id_strategy = sha256

See :ref:`parametrizemark`.

.. confval:: doctest_encoding
:type: ``str``
:default: ``"utf-8"``
Expand Down
68 changes: 65 additions & 3 deletions src/_pytest/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import enum
import fnmatch
from functools import partial
import hashlib
import inspect
import itertools
import os
Expand All @@ -26,6 +27,7 @@
from typing import Any
from typing import cast
from typing import final
from typing import get_args
from typing import Literal
from typing import NoReturn
from typing import TYPE_CHECKING
Expand All @@ -50,6 +52,7 @@
from _pytest.compat import safe_isclass
from _pytest.config import Config
from _pytest.config import hookimpl
from _pytest.config import UsageError
from _pytest.config.argparsing import Parser
from _pytest.deprecated import check_ispytest
from _pytest.fixtures import _resolve_args_directness
Expand Down Expand Up @@ -82,6 +85,11 @@
if TYPE_CHECKING:
from typing_extensions import Self

LongStrIdStrategy = Literal["short", "sha256", "legacy", "disallow"]
_LONG_STR_STRATEGIES: frozenset[LongStrIdStrategy] = frozenset(
get_args(LongStrIdStrategy)
)


def pytest_addoption(parser: Parser) -> None:
parser.addini(
Expand Down Expand Up @@ -117,6 +125,16 @@ def pytest_addoption(parser: Parser) -> None:
default=None,
help="Emit an error if non-unique parameter set IDs are detected",
)
parser.addini(
"parametrize_long_str_id_strategy",
type="string",
default="short",
help="strategy for long str/bytes parameter values in auto-generated ids\n"
"- short (default): values over 100 chars fall back to argname+index\n"
"- sha256: replace value with its sha256 hex digest\n"
"- legacy: keep the full value (for temporary backward compatibility)\n"
"- disallow: raise an error requesting explicit ids",
)


def pytest_generate_tests(metafunc: Metafunc) -> None:
Expand Down Expand Up @@ -1003,11 +1021,55 @@ def _idval(self, val: object, argname: str, idx: int) -> str:
idval = self._idval_from_hook(val, argname)
if idval is not None:
return idval
idval = self._idval_from_value(val)
if idval is not None:
return idval
if isinstance(val, str | bytes):
idval = self._apply_long_str_strategy(val, argname, idx)
if idval is not None:
return idval
else:
idval = self._idval_from_value(val)
if idval is not None:
return idval
return self._idval_from_argname(argname, idx)

def _get_long_str_strategy(self) -> LongStrIdStrategy:
if not self.config:
return "short"
value = self.config.getini("parametrize_long_str_id_strategy")
if value not in _LONG_STR_STRATEGIES:
raise UsageError(
f"Unknown parametrize_long_str_id_strategy: {value!r}. "
f"Valid values: {', '.join(sorted(_LONG_STR_STRATEGIES))}"
)
return cast(LongStrIdStrategy, value)

def _apply_long_str_strategy(
self, val: str | bytes, argname: str, idx: int
) -> str | None:
"""Apply the configured strategy for long str/bytes parameter values.

Only used for auto-generated IDs (not explicit ids=[...] or
pytest.param(id=...)).
"""
if len(val) <= 100:
return _ascii_escaped_by_config(val, self.config)
match self._get_long_str_strategy():
case "legacy":
return _ascii_escaped_by_config(val, self.config)
case "short":
return None
case "sha256":
encoded = val.encode("utf-8") if isinstance(val, str) else val
return hashlib.sha256(encoded).hexdigest()
case "disallow": # pragma: no branch -- fail() raises, confuses coverage
prefix = self._make_error_prefix()
fail(
f"{prefix}parametrize value for '{argname}' at index {idx} "
f"is too long for an auto-generated ID ({len(val)} characters). "
f"Use pytest.param(..., id=...) or parametrize(..., ids=...) "
f"to set an explicit ID, or change parametrize_long_str_id_strategy.",
pytrace=False,
)

def _idval_from_function(self, val: object, argname: str, idx: int) -> str | None:
"""Try to make an ID for a parameter in a ParameterSet using the
user-provided id callable, if given."""
Expand Down
Loading