Skip to content

fix #6881 - add policies for too long ids from a str and use short as default #13370

New issue

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

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

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
29 changes: 28 additions & 1 deletion 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 Down Expand Up @@ -106,6 +107,16 @@ def pytest_addoption(parser: Parser) -> None:
help="Disable string escape non-ASCII characters, might cause unwanted "
"side effects(use at your own risk)",
)
parser.addini(
"parametrize_long_str_id_strategy",
type="string",
default="short",
help="strategy for parameer values that result in long ids\n"
"- short: default: shorten the value normally\n"
"- hash: take a sha3 of the value for content matching ids\n"
"- legacy: keep the long id (only use this for temporary backward compatibility)\n"
"- disallow: fail and request explicit ids",
)


def pytest_generate_tests(metafunc: Metafunc) -> None:
Expand Down Expand Up @@ -989,7 +1000,23 @@ def _idval_from_value(self, val: object) -> str | None:
"""Try to make an ID for a parameter in a ParameterSet from its value,
if the value type is supported."""
if isinstance(val, (str, bytes)):
return _ascii_escaped_by_config(val, self.config)
if self.config:
strategy = self.config.getini("parametrize_long_str_id_strategy")
else:
strategy = "short"
if strategy == "legacy":
return _ascii_escaped_by_config(val, self.config)
elif strategy == "short":
if len(val) > 100:
return None
return _ascii_escaped_by_config(val, self.config)
elif strategy == "hash":
encoded = val.encode("utf-8") if isinstance(val, str) else val
return hashlib.sha256(encoded).hexdigest()
elif strategy == "disallow":
if len(val) > 100:
raise ValueError("too long as id", len(val), val)
return _ascii_escaped_by_config(val, self.config)
elif val is None or isinstance(val, (float, int, bool, complex)):
return str(val)
elif isinstance(val, re.Pattern):
Expand Down
78 changes: 78 additions & 0 deletions testing/python/metafunc.py
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,84 @@ def getini(self, name):
).make_unique_parameterset_ids()
assert result == [expected]

@pytest.mark.parametrize("id_style", ["short", "hash", "legacy"])
@pytest.mark.parametrize(
"kind",
[
pytest.param("a", id="str"),
pytest.param(b"a", id="bytes"),
],
)
def test_idmaker_long_string(self, id_style: str, kind: str | bytes) -> None:
@dataclasses.dataclass
class FakeConfig:
id_style: str | None

def getini(self, name: str) -> str | None:
if name == "parametrize_long_str_id_strategy":
return self.id_style
return None

@property
def hook(self):
return self

def pytest_make_parametrize_id(self, **kw: object) -> None:
return

maker = IdMaker(
"a",
[pytest.param(kind * 1000)],
None,
None,
cast(pytest.Config, FakeConfig(id_style)),
None,
None,
)

res = maker.make_unique_parameterset_ids()
expected = {
"legacy": "a" * 1000,
"short": "a0",
"hash": "41edece42d63e8d9bf515a9ba6932e1c20cbc9f5a5d134645adb5db1b9737ea3",
}
assert res == [expected[id_style]]

@pytest.mark.parametrize(
"kind",
[
pytest.param("a", id="str"),
pytest.param(b"a", id="bytes"),
],
)
def test_idmaker_long_string_disallow(self, kind: str | bytes) -> None:
@dataclasses.dataclass
class FakeConfig:
def getini(self, name: str) -> str | None:
if name == "parametrize_long_str_id_strategy":
return "disallow"
return None

@property
def hook(self):
return self

def pytest_make_parametrize_id(self, **kw: object) -> None:
return

maker = IdMaker(
"a",
[pytest.param(kind * 1000)],
None,
None,
cast(pytest.Config, FakeConfig()),
None,
None,
)

with pytest.raises(ValueError):
maker.make_unique_parameterset_ids()

def test_idmaker_with_param_id_and_config(self) -> None:
"""Unit test for expected behavior to create ids with pytest.param(id=...) and
disable_test_id_escaping_and_forfeit_all_rights_to_community_support
Expand Down
Loading