|
1 | 1 | # mypy: allow-untyped-defs |
2 | 2 | from __future__ import annotations |
3 | 3 |
|
| 4 | +from collections.abc import Sequence |
4 | 5 | import os |
5 | 6 | from pathlib import Path |
6 | 7 | from pathlib import PurePath |
7 | 8 | import pprint |
| 9 | +import re |
8 | 10 | import shutil |
9 | 11 | import sys |
10 | 12 | import tempfile |
|
20 | 22 | from _pytest.pathlib import symlink_or_skip |
21 | 23 | from _pytest.pytester import HookRecorder |
22 | 24 | from _pytest.pytester import Pytester |
| 25 | +from _pytest.pytester import RunResult |
23 | 26 | import pytest |
24 | 27 |
|
25 | 28 |
|
@@ -2497,7 +2500,7 @@ def test_1(): pass |
2497 | 2500 | " <Class TestIt>", |
2498 | 2501 | " <Function test_2>", |
2499 | 2502 | " <Function test_3>", |
2500 | | - " <Module test_2.py>", |
| 2503 | + # " <Module test_2.py>", |
2501 | 2504 | " <Function test_1>", |
2502 | 2505 | " <Package top2>", |
2503 | 2506 | " <Module test_1.py>", |
@@ -2702,3 +2705,126 @@ def test_1(): pass |
2702 | 2705 | ], |
2703 | 2706 | consecutive=True, |
2704 | 2707 | ) |
| 2708 | + |
| 2709 | + |
| 2710 | +class TestRequireUniqueParametrizationtIds: |
| 2711 | + @staticmethod |
| 2712 | + def _fnmatch_escape_repr(obj: Sequence[tuple[int]]) -> str: |
| 2713 | + return re.sub(r"[*?[\]]", (lambda m: f"[{m.group()}]"), repr(obj)) |
| 2714 | + |
| 2715 | + def _assert_duplicate_msg( |
| 2716 | + self, |
| 2717 | + result: RunResult, |
| 2718 | + expected_indices: dict[str, Sequence[int]], |
| 2719 | + argument_values: Sequence[str], |
| 2720 | + resolved_ids: Sequence[str], |
| 2721 | + ) -> None: |
| 2722 | + stream = result.stdout |
| 2723 | + stream.fnmatch_lines( |
| 2724 | + [ |
| 2725 | + "Duplicate parametrization IDs detected, but --require-unique-parametrization-ids is set.", |
| 2726 | + "Test name: *::test1", |
| 2727 | + "Argument names: [[]'x', 'y'[]]", |
| 2728 | + f"Argument values: {argument_values}", |
| 2729 | + f"Resolved IDs: {resolved_ids}", |
| 2730 | + f"Duplicates: {expected_indices}", |
| 2731 | + "You can fix this problem using:", |
| 2732 | + "the `ids` argument of `@pytest.mark.parametrize()` or the `id` argument of `pytest.param()`.", |
| 2733 | + ] |
| 2734 | + ) |
| 2735 | + assert result.ret != 0 |
| 2736 | + |
| 2737 | + def _convert_to_resolved_ids( |
| 2738 | + self, parametrize_args: Sequence[tuple[int, int]] |
| 2739 | + ) -> Sequence[str]: |
| 2740 | + return [f"{x}-{y}" for (x, y) in parametrize_args] |
| 2741 | + |
| 2742 | + def _convert_to_argument_values( |
| 2743 | + self, parametrize_args: Sequence[tuple[int, int]] |
| 2744 | + ) -> Sequence[str]: |
| 2745 | + return [str(t) for t in parametrize_args] |
| 2746 | + |
| 2747 | + # Change the test data to native Python types |
| 2748 | + CASES = [ |
| 2749 | + ([(1, 1), (1, 1)], {"1-1": [0, 1]}), |
| 2750 | + ([(1, 1), (1, 2), (1, 1)], {"1-1": [0, 2]}), |
| 2751 | + ([(1, 1), (2, 2), (1, 1)], {"1-1": [0, 2]}), |
| 2752 | + ([(1, 1), (2, 2), (1, 2), (2, 1), (1, 1)], {"1-1": [0, 4]}), |
| 2753 | + ] |
| 2754 | + |
| 2755 | + @pytest.mark.parametrize(("parametrize_args", "expected_indices"), CASES) |
| 2756 | + def test_cli_enables( |
| 2757 | + self, |
| 2758 | + pytester: Pytester, |
| 2759 | + parametrize_args: Sequence[tuple[int, int]], |
| 2760 | + expected_indices: dict[str, Sequence[int]], |
| 2761 | + ) -> None: |
| 2762 | + pytester.makepyfile( |
| 2763 | + f""" |
| 2764 | + import pytest |
| 2765 | +
|
| 2766 | + @pytest.mark.parametrize('x, y', {parametrize_args}) |
| 2767 | + def test1(y, x): |
| 2768 | + pass |
| 2769 | + """ |
| 2770 | + ) |
| 2771 | + result = pytester.runpytest("--require-unique-parametrization-ids") |
| 2772 | + resolved_ids = self._convert_to_resolved_ids(parametrize_args) |
| 2773 | + argument_values = self._convert_to_argument_values(parametrize_args) |
| 2774 | + self._assert_duplicate_msg( |
| 2775 | + result, expected_indices, argument_values, resolved_ids |
| 2776 | + ) |
| 2777 | + |
| 2778 | + @pytest.mark.parametrize("parametrize_args, expected_indices", CASES) |
| 2779 | + def test_ini_enables( |
| 2780 | + self, |
| 2781 | + pytester: Pytester, |
| 2782 | + parametrize_args: Sequence[tuple[int, int]], |
| 2783 | + expected_indices: dict[str, Sequence[int]], |
| 2784 | + ) -> None: |
| 2785 | + pytester.makeini( |
| 2786 | + """ |
| 2787 | + [pytest] |
| 2788 | + require_unique_parametrization_ids = true |
| 2789 | + """ |
| 2790 | + ) |
| 2791 | + pytester.makepyfile( |
| 2792 | + f""" |
| 2793 | + import pytest |
| 2794 | +
|
| 2795 | + @pytest.mark.parametrize('x, y', {parametrize_args}) |
| 2796 | + def test1(y, x): |
| 2797 | + pass |
| 2798 | + """ |
| 2799 | + ) |
| 2800 | + result = pytester.runpytest() |
| 2801 | + resolved_ids = self._convert_to_resolved_ids(parametrize_args) |
| 2802 | + argument_values = self._convert_to_argument_values(parametrize_args) |
| 2803 | + self._assert_duplicate_msg( |
| 2804 | + result, expected_indices, argument_values, resolved_ids |
| 2805 | + ) |
| 2806 | + |
| 2807 | + def test_cli_overrides_ini_false(self, pytester: Pytester) -> None: |
| 2808 | + """CLI True should override ini False.""" |
| 2809 | + pytester.makeini( |
| 2810 | + """ |
| 2811 | + [pytest] |
| 2812 | + require_unique_parametrization_ids = false |
| 2813 | + """ |
| 2814 | + ) |
| 2815 | + pytester.makepyfile( |
| 2816 | + """ |
| 2817 | + import pytest |
| 2818 | +
|
| 2819 | + @pytest.mark.parametrize('x, y', [(1,1), (1,1)]) |
| 2820 | + def test1(y, x): |
| 2821 | + pass |
| 2822 | + """ |
| 2823 | + ) |
| 2824 | + result = pytester.runpytest("--require-unique-parametrization-ids") |
| 2825 | + parametrization_args = [(1, 1), (1, 1)] |
| 2826 | + resolved_ids = self._convert_to_resolved_ids(parametrization_args) |
| 2827 | + argument_values = self._convert_to_argument_values(parametrization_args) |
| 2828 | + self._assert_duplicate_msg( |
| 2829 | + result, {"1-1": [0, 1]}, argument_values, resolved_ids |
| 2830 | + ) |
0 commit comments