From 00024402062875ad088582534f69ea1a4a7e77f0 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Fri, 11 Apr 2025 21:25:24 +0200 Subject: [PATCH] WIP: fix #6881 - add policies for handling long test ids --- src/_pytest/python.py | 29 +++++++++++++- testing/python/metafunc.py | 78 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 1 deletion(-) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 902bcfade9f..1e654d6d12f 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -16,6 +16,7 @@ import enum import fnmatch from functools import partial +import hashlib import inspect import itertools import os @@ -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: @@ -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): diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index e8b345aecc6..e110d397f2c 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -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