diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index 40415aa..c535601 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -33,7 +33,14 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Slugify GITHUB_REPOSITORY + - name: Slugify GITHUB_REPOSITORY (win) + if: ${{ matrix.os == 'windows-latest' }} + run: | + $slug = $env:GITHUB_REPOSITORY -replace '/', '_' + echo "GITHUB_REPOSITORY_SLUG=$slug" | tee -Append $env:GITHUB_ENV + + - name: Slugify GITHUB_REPOSITORY (non-win) + if: ${{ matrix.os != 'windows-latest' }} run: echo "GITHUB_REPOSITORY_SLUG=${GITHUB_REPOSITORY////_}" >> $GITHUB_ENV - name: Set up Python ${{ matrix.python-version }} @@ -51,7 +58,7 @@ jobs: - name: Test with tox env: FORCE_COLOR: 1 - PYTEST_CI_ARGS: --cov-report=xml --cov-report=html --junitxml=test_artifacts/test_report.xml --color=yes + PYTEST_CI_ARGS: --cov-report=xml --junitxml=test_artifacts/test_report.xml --color=yes run: | TOX_ENV=$(echo "py${{ matrix.python-version }}" | tr -d .) tox -e $TOX_ENV diff --git a/CHANGELOG.md b/CHANGELOG.md index ea52506..3c1916d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,38 @@ - Support for Python 3.6 & 3.7 (#23) +## [1.1.4] - 2023-11-06 + +This is a small bugfix release. + +### Fixed + +* `anis-campos/fix_is_pytest_fixture`: (https://github.com/pylint-dev/pylint-pytest/pull/2) + Astroid has different semantics when using `import pytest` or `from pytest import ...`, + which affects the detection of pytest fixtures. + +### Improved + +* `pre-commit`: (https://github.com/pylint-dev/pylint-pytest/pull/20) + * Added more checks to the `pre-commit` hook. + ```yaml + repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + hooks: + - id: check-yaml + - id: check-toml + - id: check-vcs-permalinks + - id: check-shebang-scripts-are-executable + - id: name-tests-test + - repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt + - id: yamlfmt + - repo: local + hooks: + - id: python-no-log-fatal + name: avoid logger.fatal( + ``` + * Unified formatting (always expanded arrays; not covered by linters, sadly) + ## [1.1.3] - 2023-10-23 This is the first release after maintenance was assumed by https://github.com/stdedos. diff --git a/pylint_pytest/checkers/fixture.py b/pylint_pytest/checkers/fixture.py index 374b8a4..1f981d4 100644 --- a/pylint_pytest/checkers/fixture.py +++ b/pylint_pytest/checkers/fixture.py @@ -114,10 +114,10 @@ def visit_module(self, node): is_test_module = True break + stdout, stderr = sys.stdout, sys.stderr try: with open(os.devnull, "w") as devnull: # suppress any future output from pytest - stdout, stderr = sys.stdout, sys.stderr sys.stderr = sys.stdout = devnull # run pytest session with customized plugin to collect fixtures @@ -208,7 +208,7 @@ def visit_functiondef(self, node): for arg in node.args.args: self._invoked_with_func_args.add(arg.name) - # pylint: disable=bad-staticmethod-argument + # pylint: disable=bad-staticmethod-argument,too-many-branches # The function itself is an if-return logic. @staticmethod def patch_add_message( self, msgid, line=None, node=None, args=None, confidence=None, col_offset=None @@ -265,9 +265,18 @@ def patch_add_message( msgid == "unused-argument" and _can_use_fixture(node.parent.parent) and isinstance(node.parent, astroid.Arguments) - and node.name in FixtureChecker._pytest_fixtures ): - return + if node.name in FixtureChecker._pytest_fixtures: + # argument is used as a fixture + return + + fixnames = ( + arg.name for arg in node.parent.args if arg.name in FixtureChecker._pytest_fixtures + ) + for fixname in fixnames: + if node.name in FixtureChecker._pytest_fixtures[fixname][0].argnames: + # argument is used by a fixture + return # check W0621 redefined-outer-name if ( diff --git a/pyproject.toml b/pyproject.toml index 18cc087..870ea27 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ module = [ check_untyped_defs = true [tool.pytest.ini_options] -addopts = "--verbose --cov-config=pyproject.toml" +addopts = "--verbose --cov-config=pyproject.toml --cov-report=html" [tool.ruff] # ruff is less lenient than pylint and does not make any exceptions diff --git a/setup.py b/setup.py index 821cd3b..84c19f2 100755 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ setup( name="pylint-pytest", - version="1.1.3", + version="1.1.4", author="Stavros Ntentos", author_email="133706+stdedos@users.noreply.github.com", license="MIT", diff --git a/tests/base_tester.py b/tests/base_tester.py index 38853c8..a7b1d6c 100644 --- a/tests/base_tester.py +++ b/tests/base_tester.py @@ -1,6 +1,7 @@ import os import sys from abc import ABC +from pathlib import Path from pprint import pprint from typing import Any, Dict, List @@ -21,6 +22,11 @@ pylint_pytest.checkers.fixture.FILE_NAME_PATTERNS = ("*",) +def get_test_root_path() -> Path: + """Assumes ``base_tester.py`` is at ``/tests``.""" + return Path(__file__).parent + + class BasePytestTester(ABC): CHECKER_CLASS = BaseChecker IMPACTED_CHECKER_CLASSES: List[BaseChecker] = [] @@ -41,7 +47,10 @@ def run_linter(self, enable_plugin): # pylint: disable-next=protected-access target_test_file = sys._getframe(1).f_code.co_name.replace("test_", "", 1) file_path = os.path.join( - os.getcwd(), "tests", "input", self.MSG_ID, target_test_file + ".py" + get_test_root_path(), + "input", + self.MSG_ID, + target_test_file + ".py", ) with open(file_path) as fin: diff --git a/tests/base_tester_test.py b/tests/base_tester_test.py index 4cf43ff..b40d510 100644 --- a/tests/base_tester_test.py +++ b/tests/base_tester_test.py @@ -1,9 +1,14 @@ import pytest -from base_tester import BasePytestTester +from base_tester import BasePytestTester, get_test_root_path # pylint: disable=unused-variable +def test_get_test_root_path(): + assert get_test_root_path().name == "tests" + assert (get_test_root_path() / "input").is_dir() + + def test_init_subclass_valid_msg_id(): some_string = "some_string" diff --git a/tests/input/unused-argument/func_param_as_fixture_arg.py b/tests/input/unused-argument/func_param_as_fixture_arg.py new file mode 100644 index 0000000..90f0404 --- /dev/null +++ b/tests/input/unused-argument/func_param_as_fixture_arg.py @@ -0,0 +1,34 @@ +""" +This module illustrates a situation in which unused-argument should be +suppressed, but is not. +""" + +import pytest + + +@pytest.fixture +def myfix(arg): + """A fixture that requests a function param""" + print("arg is ", arg) + return True + + +@pytest.mark.parametrize("arg", [1, 2, 3]) +def test_myfix(myfix, arg): + """A test function that uses the param through a fixture""" + assert myfix + + +@pytest.mark.parametrize("narg", [4, 5, 6]) +def test_nyfix(narg): # unused-argument + """A test function that does not use its param""" + assert True + + +@pytest.mark.parametrize("arg", [1, 2, 3]) +def test_narg_is_used_nowhere(myfix, narg): + """ + A test function that does not use its param (``narg``): + Not itself, nor through a fixture (``myfix``). + """ + assert myfix diff --git a/tests/test_unused_argument.py b/tests/test_unused_argument.py index 7ca2307..a164672 100644 --- a/tests/test_unused_argument.py +++ b/tests/test_unused_argument.py @@ -29,3 +29,8 @@ def test_caller_not_a_test_func(self, enable_plugin): def test_args_and_kwargs(self, enable_plugin): self.run_linter(enable_plugin) self.verify_messages(2) + + @pytest.mark.parametrize("enable_plugin", [True, False]) + def test_func_param_as_fixture_arg(self, enable_plugin): + self.run_linter(enable_plugin) + self.verify_messages(2 if enable_plugin else 3)