From af9b1dcc24ab1c71ae4109323ceb5270738bb5e1 Mon Sep 17 00:00:00 2001 From: Tanya Agarwal Date: Sun, 8 Oct 2023 22:56:31 +0530 Subject: [PATCH] Duplicated parameters in parametrize marker (#11489) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Zac Hatfield-Dodds --- AUTHORS | 1 + changelog/11456.bugfix.rst | 4 ++++ src/_pytest/python.py | 12 +++++++++++- testing/acceptance_test.py | 39 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 changelog/11456.bugfix.rst diff --git a/AUTHORS b/AUTHORS index f8c66cae9..3c23d2277 100644 --- a/AUTHORS +++ b/AUTHORS @@ -367,6 +367,7 @@ Tadek Teleżyński Takafumi Arakaki Taneli Hukkinen Tanvi Mehta +Tanya Agarwal Tarcisio Fischer Tareq Alayan Tatiana Ovary diff --git a/changelog/11456.bugfix.rst b/changelog/11456.bugfix.rst new file mode 100644 index 000000000..77a2ccfb0 --- /dev/null +++ b/changelog/11456.bugfix.rst @@ -0,0 +1,4 @@ +Parametrized tests now *really do* ensure that the ids given to each input are unique - for +example, ``a, a, a0`` now results in ``a1, a2, a0`` instead of the previous (buggy) ``a0, a1, a0``. +This necessarily means changing nodeids where these were previously colliding, and for +readability adds an underscore when non-unique ids end in a number. diff --git a/src/_pytest/python.py b/src/_pytest/python.py index cbb82e390..f54bbb379 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -1002,8 +1002,18 @@ class IdMaker: # Suffix non-unique IDs to make them unique. for index, id in enumerate(resolved_ids): if id_counts[id] > 1: - resolved_ids[index] = f"{id}{id_suffixes[id]}" + suffix = "" + if id[-1].isdigit(): + suffix = "_" + new_id = f"{id}{suffix}{id_suffixes[id]}" + while new_id in set(resolved_ids): + id_suffixes[id] += 1 + new_id = f"{id}{suffix}{id_suffixes[id]}" + resolved_ids[index] = new_id id_suffixes[id] += 1 + assert len(resolved_ids) == len( + set(resolved_ids) + ), f"Internal error: {resolved_ids=}" return resolved_ids def _resolve_ids(self) -> Iterable[str]: diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 429fb4e43..d597311ae 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -341,6 +341,45 @@ class TestGeneralUsage: assert res.ret == 0 res.stdout.fnmatch_lines(["*1 passed*"]) + def test_direct_addressing_selects_duplicates(self, pytester: Pytester) -> None: + p = pytester.makepyfile( + """ + import pytest + + @pytest.mark.parametrize("a", [1, 2, 10, 11, 2, 1, 12, 11]) + def test_func(a): + pass + """ + ) + result = pytester.runpytest(p) + result.assert_outcomes(failed=0, passed=8) + + def test_direct_addressing_selects_duplicates_1(self, pytester: Pytester) -> None: + p = pytester.makepyfile( + """ + import pytest + + @pytest.mark.parametrize("a", [1, 2, 10, 11, 2, 1, 12, 1_1,2_1]) + def test_func(a): + pass + """ + ) + result = pytester.runpytest(p) + result.assert_outcomes(failed=0, passed=9) + + def test_direct_addressing_selects_duplicates_2(self, pytester: Pytester) -> None: + p = pytester.makepyfile( + """ + import pytest + + @pytest.mark.parametrize("a", ["a","b","c","a","a1"]) + def test_func(a): + pass + """ + ) + result = pytester.runpytest(p) + result.assert_outcomes(failed=0, passed=5) + def test_direct_addressing_notfound(self, pytester: Pytester) -> None: p = pytester.makepyfile( """