Skip to content

Fix pytest.mark.parametrize rules to check calls instead of decorators#14515

Merged
MichaReiser merged 10 commits intoastral-sh:mainfrom
harupy:fix-pytest-parametrize
Nov 25, 2024
Merged

Fix pytest.mark.parametrize rules to check calls instead of decorators#14515
MichaReiser merged 10 commits intoastral-sh:mainfrom
harupy:fix-pytest-parametrize

Conversation

@harupy
Copy link
Contributor

@harupy harupy commented Nov 21, 2024

Summary

Currently, rules for pytest.mark.parametrize (e.g. /pytest-parametrize-names-wrong-type (PT006)) only check decorators, but pytest.mark.parametrize can appear as a function call as show below.

# To parametrize all tests in a module, you can assign to pytest mark


import pytest

pytestmark = pytest.mark.parametrize("n,expected", [(1, 2), (3, 4)])


class TestClass:
    def test_simple_case(self, n, expected):
        assert n + 1 == expected

    def test_weird_simple_case(self, n, expected):
        assert (n * 1) + 1 == expected

Test Plan

Existing tests and a new test case to ensure the fix is valid.

@harupy
Copy link
Contributor Author

harupy commented Nov 21, 2024

The upstream rule also checks calls:

https://github.com/m-burst/flake8-pytest-style/blob/2addab8b533ea101e25d022a37dc32d89db6c688/flake8_pytest_style/visitors/parametrize.py#L130-L132

    def visit_Call(self, node: ast.Call) -> None:
        if is_parametrize_call(node):
            self._check_parametrize_call(node)

@github-actions
Copy link
Contributor

github-actions bot commented Nov 21, 2024

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

ℹ️ ecosystem check detected linter changes. (+50 -0 violations, +0 -0 fixes in 2 projects; 53 projects unchanged)

apache/airflow (+48 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --ignore RUF9 --output-format concise --preview --select ALL

+ helm_tests/airflow_aux/test_annotations.py:249:5: PT006 Wrong type passed to first argument of `@pytest.mark.parametrize`; expected `tuple`
+ providers/tests/dbt/cloud/hooks/test_dbt.py:157:18: PT006 Wrong type passed to first argument of `@pytest.mark.parametrize`; expected `tuple`
+ providers/tests/dbt/cloud/hooks/test_dbt.py:168:18: PT006 Wrong type passed to first argument of `@pytest.mark.parametrize`; expected `tuple`
+ providers/tests/dbt/cloud/hooks/test_dbt.py:178:18: PT006 Wrong type passed to first argument of `@pytest.mark.parametrize`; expected `tuple`
+ providers/tests/dbt/cloud/hooks/test_dbt.py:201:18: PT006 Wrong type passed to first argument of `@pytest.mark.parametrize`; expected `tuple`
+ providers/tests/dbt/cloud/hooks/test_dbt.py:216:18: PT006 Wrong type passed to first argument of `@pytest.mark.parametrize`; expected `tuple`
+ providers/tests/dbt/cloud/hooks/test_dbt.py:235:18: PT006 Wrong type passed to first argument of `@pytest.mark.parametrize`; expected `tuple`
+ providers/tests/dbt/cloud/hooks/test_dbt.py:254:18: PT006 Wrong type passed to first argument of `@pytest.mark.parametrize`; expected `tuple`
+ providers/tests/dbt/cloud/hooks/test_dbt.py:273:18: PT006 Wrong type passed to first argument of `@pytest.mark.parametrize`; expected `tuple`
+ providers/tests/dbt/cloud/hooks/test_dbt.py:294:18: PT006 Wrong type passed to first argument of `@pytest.mark.parametrize`; expected `tuple`
+ providers/tests/dbt/cloud/hooks/test_dbt.py:315:18: PT006 Wrong type passed to first argument of `@pytest.mark.parametrize`; expected `tuple`
+ providers/tests/dbt/cloud/hooks/test_dbt.py:334:18: PT006 Wrong type passed to first argument of `@pytest.mark.parametrize`; expected `tuple`
+ providers/tests/dbt/cloud/hooks/test_dbt.py:356:18: PT006 Wrong type passed to first argument of `@pytest.mark.parametrize`; expected `tuple`
+ providers/tests/dbt/cloud/hooks/test_dbt.py:388:18: PT006 Wrong type passed to first argument of `@pytest.mark.parametrize`; expected `tuple`
+ providers/tests/dbt/cloud/hooks/test_dbt.py:423:18: PT006 Wrong type passed to first argument of `@pytest.mark.parametrize`; expected `tuple`
+ providers/tests/dbt/cloud/hooks/test_dbt.py:453:18: PT006 Wrong type passed to first argument of `@pytest.mark.parametrize`; expected `tuple`
+ providers/tests/dbt/cloud/hooks/test_dbt.py:458:18: PT006 Wrong type passed to first argument of `@pytest.mark.parametrize`; expected `tuple`
+ providers/tests/dbt/cloud/hooks/test_dbt.py:512:18: PT006 Wrong type passed to first argument of `@pytest.mark.parametrize`; expected `tuple`
+ providers/tests/dbt/cloud/hooks/test_dbt.py:534:18: PT006 Wrong type passed to first argument of `@pytest.mark.parametrize`; expected `tuple`
+ providers/tests/dbt/cloud/hooks/test_dbt.py:559:18: PT006 Wrong type passed to first argument of `@pytest.mark.parametrize`; expected `tuple`
+ providers/tests/dbt/cloud/hooks/test_dbt.py:586:18: PT006 Wrong type passed to first argument of `@pytest.mark.parametrize`; expected `tuple`
+ providers/tests/dbt/cloud/hooks/test_dbt.py:603:18: PT006 Wrong type passed to first argument of `@pytest.mark.parametrize`; expected `tuple`
+ providers/tests/dbt/cloud/hooks/test_dbt.py:624:18: PT006 Wrong type passed to first argument of `@pytest.mark.parametrize`; expected `tuple`
+ providers/tests/dbt/cloud/hooks/test_dbt.py:692:18: PT006 Wrong type passed to first argument of `@pytest.mark.parametrize`; expected `tuple`
+ providers/tests/dbt/cloud/hooks/test_dbt.py:711:18: PT006 Wrong type passed to first argument of `@pytest.mark.parametrize`; expected `tuple`
... 23 additional changes omitted for rule PT006
+ providers/tests/microsoft/azure/hooks/test_data_factory.py:151:13: PT007 Wrong values type in `@pytest.mark.parametrize` expected `list` of `tuple`

pypa/setuptools (+2 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --ignore RUF9 --output-format concise --preview

+ pkg_resources/tests/test_working_set.py:107:9: PT006 Wrong type passed to first argument of `@pytest.mark.parametrize`; expected `tuple`
+ setuptools/tests/test_egg_info.py:308:17: PT006 Wrong type passed to first argument of `@pytest.mark.parametrize`; expected `tuple`

Changes by rule (2 rules affected)

code total + violation - violation + fix - fix
PT006 49 49 0 0 0
PT007 1 1 0 0 0

@harupy harupy force-pushed the fix-pytest-parametrize branch from fda25ed to 3f385b6 Compare November 21, 2024 14:09
harupy and others added 2 commits November 22, 2024 13:48
…rize.rs

Co-authored-by: Micha Reiser <micha@reiser.io>
@harupy harupy requested a review from MichaReiser November 22, 2024 13:23
}
}
pub(crate) fn parametrize(checker: &mut Checker, call: &ExprCall) {
if !is_pytest_parametrize(call, checker.semantic()) {
Copy link
Contributor Author

@harupy harupy Nov 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated to this PR, but PT rules don't have a seen_module guard. Shoud we add it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah we could.

Copy link
Contributor Author

@harupy harupy Nov 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like a nice good first issue.

Comment on lines +803 to +817
if checker.enabled(Rule::PytestParametrizeNamesWrongType) {
if let Some(names) = call.arguments.find_argument("argnames", 0) {
check_names(checker, call, names);
}
}
if checker.enabled(Rule::PytestParametrizeValuesWrongType) {
let names = call.arguments.find_argument("argnames", 0);
let values = call.arguments.find_argument("argvalues", 1);
if let (Some(names), Some(values)) = (names, values) {
check_values(checker, names, values);
}
}
if checker.enabled(Rule::PytestDuplicateParametrizeTestCases) {
if let Some(values) = call.arguments.find_argument("argvalues", 1) {
check_duplicates(checker, values);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This aligns with the upstream implementation:

https://github.com/m-burst/flake8-pytest-style/blob/2addab8b533ea101e25d022a37dc32d89db6c688/flake8_pytest_style/utils.py#L96

def extract_parametrize_call_args(node: ast.Call) -> Optional[ParametrizeArgs]:
    """Extracts argnames, argvalues and ids from a parametrize call."""
    args = get_simple_call_args(node)

    names_arg = args.get_argument('argnames', 0)
    if names_arg is None:
        return None

    values_arg = args.get_argument('argvalues', 1)
    ids_arg = args.get_argument('ids')
    return ParametrizeArgs(names_arg, values_arg, ids_arg)

@MichaReiser MichaReiser added the rule Implementing or modifying a lint rule label Nov 25, 2024
@MichaReiser
Copy link
Member

Thanks for the how-to-guide and API reference. That helps a lot with building the required context to review the rule. I really appreciate it :)

Copy link
Member

@MichaReiser MichaReiser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great. Did you already look through the ecosystem changes? I can do it, if you haven't.

I think we should gate this change behind preview mode because I consider this a "significant increase in scope" and we should only make such changes in minor releases.

@harupy
Copy link
Contributor Author

harupy commented Nov 25, 2024

@MichaReiser Thanks for the comment. This PR has the two changes. Do we want to gate both of them?

  1. Only calls in decorators are covered -> Any calls are covered.
  2. Only positional arguments are covered -> Both positional and keyword arguments are covered.

@MichaReiser
Copy link
Member

Oh right. Making 1. preview-only for now seems more important to me than 2. We can have a look at the ecosystem change to understand how much impact 2 has but I think it's fine to not gate 2.

@harupy
Copy link
Contributor Author

harupy commented Nov 25, 2024

For Airflow, 2 has more impacts (more changes) than 1.

@MichaReiser
Copy link
Member

Right, I should have taken a closer look at the ecosystem results. We should then probably make 2 a preview style change as well. It's a bit annoying codewise but feels most in line with our versioning policy

@harupy harupy requested a review from MichaReiser November 25, 2024 12:29
@harupy harupy force-pushed the fix-pytest-parametrize branch from 2f3d825 to 509c5c6 Compare November 25, 2024 12:33
@MichaReiser MichaReiser added the preview Related to preview mode features label Nov 25, 2024
@MichaReiser MichaReiser merged commit fa22bd6 into astral-sh:main Nov 25, 2024
@harupy harupy deleted the fix-pytest-parametrize branch November 25, 2024 12:58
MichaReiser added a commit that referenced this pull request Jan 8, 2025
…ze` calls" (`PT006`) (#15327)

Co-authored-by: Micha Reiser <micha@reiser.io>
Resolves #15324. Stabilizes the behavior changes introduced in #14515.
MichaReiser added a commit that referenced this pull request Jan 8, 2025
…ze` calls" (`PT006`) (#15327)

Co-authored-by: Micha Reiser <micha@reiser.io>
Resolves #15324. Stabilizes the behavior changes introduced in #14515.
MichaReiser added a commit that referenced this pull request Jan 8, 2025
…ze` calls" (`PT006`) (#15327)

Co-authored-by: Micha Reiser <micha@reiser.io>
Resolves #15324. Stabilizes the behavior changes introduced in #14515.
MichaReiser added a commit that referenced this pull request Jan 9, 2025
…ze` calls" (`PT006`) (#15327)

Co-authored-by: Micha Reiser <micha@reiser.io>
Resolves #15324. Stabilizes the behavior changes introduced in #14515.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview Related to preview mode features rule Implementing or modifying a lint rule

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants