Skip to content

Commit 421809d

Browse files
committed
Merge branch 'master' into migrate-to-setup.cfg
# By Stavros Ntentos (14) and others # Via GitHub (10) and Stavros Ntentos (1) * master: Update/Prepare Changelog for v1.1.7 use pylint v3 in the repo remove support of pylint v1 Support pylint v3 Release v1.1.6 Minor `.github/ISSUE_TEMPLATE/bug_report.md` improvement Release v1.1.5 Improve copy-paste docstring from 8756cc4 Fix tests running via PyCharm Move `--cov-report=html` to `addopts` Windows Artifacts have incorrect Slugification Release v1.1.4 Extend coverage for `if node.name in FixtureChecker._pytest_fixtures[fixname][0].argnames:` Make `nsp/nate/better-unused-argument` merge-able with `origin/master` Improve `F6401`:`cannot-enumerate-pytest-fixtures`: 🐛 Ignore collection failures in non-tests removes more false positives for unused-argument Signed-off-by: Stavros Ntentos <133706+stdedos@users.noreply.github.com> # Conflicts: # .github/workflows/run-tests.yaml # CHANGELOG.md # setup.py # tests/base_tester.py # tox.ini Additionally, some minor format modifications at `CHANGELOG.md`
2 parents 3cbbfc6 + 48212e2 commit 421809d

File tree

14 files changed

+255
-57
lines changed

14 files changed

+255
-57
lines changed

.github/ISSUE_TEMPLATE/bug_report.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,21 @@ A clear and concise description of what the bug is.
1212

1313
**To Reproduce**
1414
Package versions
15-
- pylint
16-
- pytest
17-
- pylint-pytest
15+
16+
<!-- e.g., get them by running
17+
pip list |& grep -E '\b(pylint|pytest|pylint-pytest)\b' | sed 's/^/* /'
18+
-->
19+
20+
* pylint:
21+
* pytest:
22+
* pylint-pytest:
1823

1924
(add any relevant pylint/pytest plugin here)
2025

2126
Folder structure
22-
```
27+
```console
28+
$ tree -L 2
29+
2330
```
2431

2532
File content
@@ -32,7 +39,7 @@ pylint output with the plugin
3239

3340
(Optional) pytest output from fixture collection
3441
```bash
35-
$ pytest <path/to/test/module.py> --fixtures --collect-only
42+
$ pytest --fixtures --collect-only <path/to/test/module.py>
3643
<paste output here, can omit the built-ins>
3744
```
3845

.github/workflows/run-tests.yaml

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,14 @@ jobs:
3333
steps:
3434
- uses: actions/checkout@v3
3535

36-
- name: Slugify GITHUB_REPOSITORY
36+
- name: Slugify GITHUB_REPOSITORY (win)
37+
if: ${{ matrix.os == 'windows-latest' }}
38+
run: |
39+
$slug = $env:GITHUB_REPOSITORY -replace '/', '_'
40+
echo "GITHUB_REPOSITORY_SLUG=$slug" | tee -Append $env:GITHUB_ENV
41+
42+
- name: Slugify GITHUB_REPOSITORY (non-win)
43+
if: ${{ matrix.os != 'windows-latest' }}
3744
run: echo "GITHUB_REPOSITORY_SLUG=${GITHUB_REPOSITORY////_}" >> $GITHUB_ENV
3845

3946
- name: Set up Python ${{ matrix.python-version }}
@@ -51,11 +58,8 @@ jobs:
5158
- name: Test with tox
5259
env:
5360
FORCE_COLOR: 1
54-
PYTEST_CI_ARGS: --cov-report=xml --cov-report=html --junitxml=test_artifacts/test_report.xml --color=yes
55-
run: |
56-
TOX_ENV=$(echo "py${{ matrix.python-version }}" | tr -d .)
57-
tox -e $TOX_ENV
58-
shell: bash
61+
PYTEST_CI_ARGS: --cov-report=xml --junitxml=test_artifacts/test_report.xml --color=yes
62+
run: tox --skip-missing-interpreters=true
5963

6064
- name: Upload coverage reports to Codecov
6165
uses: codecov/codecov-action@v3

CHANGELOG.md

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,75 @@
44

55
### Added
66

7-
- Support for Python 3.12 (#24)
8-
- Support for Pylint 3 (#24)
7+
* Support for Python 3.12 (https://github.com/pylint-dev/pylint-pytest/pull/24)
8+
* Support for Pylint 3 (https://github.com/pylint-dev/pylint-pytest/pull/24)
99

1010
### Removed
1111

12-
- Support for Python 3.6 & 3.7 (#23)
12+
* Support for Python 3.6 & 3.7 (https://github.com/pylint-dev/pylint-pytest/pull/23)
13+
14+
## [1.1.7] - 2023-12-04
15+
16+
This is a small release to support additionally Pylint v3.
17+
It should be noted, however, that for linting, Pylint must be v3 or newer (due to backwards-incompatible changes).
18+
19+
### Fixed
20+
21+
* Support pylint v3 and drop v1 (https://github.com/pylint-dev/pylint-pytest/pull/27)
22+
23+
## [1.1.6] - 2023-11-20
24+
25+
This is a small bugfix release.
26+
27+
This will probably be the last bugfix release in the v1 series.
28+
We MAY support Python 3.12 in the v1 series if support appears to be trivial.
29+
30+
### Fixed
31+
32+
* 🐛 Ignore collection failures in non-tests (https://github.com/pylint-dev/pylint-pytest/pull/15)
33+
* Minor `.github/ISSUE_TEMPLATE/bug_report.md` improvement (https://github.com/pylint-dev/pylint-pytest/commit/22650f9912bcdc6a1bc4b3166f70bba7339aba7c)
34+
35+
## [1.1.5] - 2023-11-13
36+
37+
This is a small bugfix release.
38+
39+
### Fixed
40+
41+
* removes more false positives for unused-argument (https://github.com/pylint-dev/pylint-pytest/pull/21)
42+
* A collection of minor improvements to tests (https://github.com/pylint-dev/pylint-pytest/pull/26)
43+
* Windows Artifacts have incorrect Slugification (https://github.com/pylint-dev/pylint-pytest/pull/25)
44+
45+
## [1.1.4] - 2023-11-06
46+
47+
This is a small bugfix release.
48+
49+
### Fixed
50+
51+
* `anis-campos/fix_is_pytest_fixture` (https://github.com/pylint-dev/pylint-pytest/pull/2)
52+
Astroid has different semantics when using `import pytest` or `from pytest import ...`,
53+
which affects the detection of pytest fixtures.
54+
55+
### Improved
56+
57+
* `pre-commit`: (https://github.com/pylint-dev/pylint-pytest/pull/20)
58+
* Added more checks to the `pre-commit` hook.
59+
```yaml
60+
repos:
61+
- repo: https://github.com/pre-commit/pre-commit-hooks
62+
hooks:
63+
- id: check-yaml
64+
- id: check-toml
65+
- id: check-vcs-permalinks
66+
- id: check-shebang-scripts-are-executable
67+
- id: name-tests-test
68+
- repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt
69+
- id: yamlfmt
70+
- repo: local
71+
hooks:
72+
- id: python-no-log-fatal
73+
name: avoid logger.fatal(
74+
```
75+
* Unified formatting (always expanded arrays; not covered by linters, sadly)
1376
1477
## [1.1.3] - 2023-10-23
1578

pylint_pytest/checkers/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
from pylint.checkers import BaseChecker
22

3+
from pylint_pytest.utils import PYLINT_VERSION_MAJOR
4+
35

46
class BasePytestChecker(BaseChecker):
7+
if PYLINT_VERSION_MAJOR < 3:
8+
# todo(maybe-remove): if we only support pylint>=3
9+
# Since https://github.com/pylint-dev/pylint/pull/8404, pylint does not need this
10+
# __implements__ pattern. keeping it for retro compatibility with pylint==2.x
11+
# pylint: disable=import-outside-toplevel,no-name-in-module
12+
from pylint.interfaces import IAstroidChecker
13+
14+
__implements__ = IAstroidChecker
15+
516
name = "pylint-pytest"

pylint_pytest/checkers/fixture.py

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import fnmatch
2-
import os
2+
import io
33
import sys
44
from pathlib import Path
55
from typing import Set, Tuple
66

77
import astroid
8-
import pylint
98
import pytest
109
from pylint.checkers.variables import VariablesChecker
1110

@@ -66,8 +65,8 @@ class FixtureChecker(BasePytestChecker):
6665
"F6401": (
6766
(
6867
"pylint-pytest plugin cannot enumerate and collect pytest fixtures. "
69-
"Please run `pytest --fixtures --collect-only path/to/current/module.py`"
70-
" and resolve any potential syntax error or package dependency issues"
68+
"Please run `pytest --fixtures --collect-only %s` and resolve "
69+
"any potential syntax error or package dependency issues. stdout: %s. stderr: %s."
7170
),
7271
"cannot-enumerate-pytest-fixtures",
7372
"Used when pylint-pytest has been unable to enumerate and collect pytest fixtures.",
@@ -114,11 +113,12 @@ def visit_module(self, node):
114113
is_test_module = True
115114
break
116115

116+
stdout, stderr = sys.stdout, sys.stderr
117117
try:
118-
with open(os.devnull, "w") as devnull:
118+
with io.StringIO() as captured_stdout, io.StringIO() as captured_stderr:
119119
# suppress any future output from pytest
120-
stdout, stderr = sys.stdout, sys.stderr
121-
sys.stderr = sys.stdout = devnull
120+
sys.stderr = captured_stderr
121+
sys.stdout = captured_stdout
122122

123123
# run pytest session with customized plugin to collect fixtures
124124
fixture_collector = FixtureCollector()
@@ -141,8 +141,32 @@ def visit_module(self, node):
141141

142142
FixtureChecker._pytest_fixtures = fixture_collector.fixtures
143143

144-
if (ret != pytest.ExitCode.OK or fixture_collector.errors) and is_test_module:
145-
self.add_message("cannot-enumerate-pytest-fixtures", node=node)
144+
legitimate_failure_paths = set(
145+
collection_report.nodeid
146+
for collection_report in fixture_collector.errors
147+
if any(
148+
fnmatch.fnmatch(
149+
Path(collection_report.nodeid).name,
150+
pattern,
151+
)
152+
for pattern in FILE_NAME_PATTERNS
153+
)
154+
)
155+
if (ret != pytest.ExitCode.OK or legitimate_failure_paths) and is_test_module:
156+
files_to_report = {
157+
str(Path(x).absolute().relative_to(Path.cwd()))
158+
for x in legitimate_failure_paths | {node.file}
159+
}
160+
161+
self.add_message(
162+
"cannot-enumerate-pytest-fixtures",
163+
args=(
164+
" ".join(files_to_report),
165+
captured_stdout.getvalue(),
166+
captured_stderr.getvalue(),
167+
),
168+
node=node,
169+
)
146170
finally:
147171
# restore output devices
148172
sys.stdout, sys.stderr = stdout, stderr
@@ -208,7 +232,7 @@ def visit_functiondef(self, node):
208232
for arg in node.args.args:
209233
self._invoked_with_func_args.add(arg.name)
210234

211-
# pylint: disable=bad-staticmethod-argument
235+
# pylint: disable=bad-staticmethod-argument # The function itself is an if-return logic.
212236
@staticmethod
213237
def patch_add_message(
214238
self, msgid, line=None, node=None, args=None, confidence=None, col_offset=None
@@ -265,9 +289,18 @@ def patch_add_message(
265289
msgid == "unused-argument"
266290
and _can_use_fixture(node.parent.parent)
267291
and isinstance(node.parent, astroid.Arguments)
268-
and node.name in FixtureChecker._pytest_fixtures
269292
):
270-
return
293+
if node.name in FixtureChecker._pytest_fixtures:
294+
# argument is used as a fixture
295+
return
296+
297+
fixnames = (
298+
arg.name for arg in node.parent.args if arg.name in FixtureChecker._pytest_fixtures
299+
)
300+
for fixname in fixnames:
301+
if node.name in FixtureChecker._pytest_fixtures[fixname][0].argnames:
302+
# argument is used by a fixture
303+
return
271304

272305
# check W0621 redefined-outer-name
273306
if (
@@ -278,10 +311,4 @@ def patch_add_message(
278311
):
279312
return
280313

281-
if int(pylint.__version__.split(".")[0]) >= 2:
282-
FixtureChecker._original_add_message(
283-
self, msgid, line, node, args, confidence, col_offset
284-
)
285-
else:
286-
# python2 + pylint1.9 backward compatibility
287-
FixtureChecker._original_add_message(self, msgid, line, node, args, confidence)
314+
FixtureChecker._original_add_message(self, msgid, line, node, args, confidence, col_offset)

pylint_pytest/utils.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import inspect
22

33
import astroid
4+
import pylint
5+
6+
PYLINT_VERSION_MAJOR = int(pylint.__version__.split(".")[0])
47

58

69
def _is_pytest_mark_usefixtures(decorator):

pyproject.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ module = [
127127
check_untyped_defs = true
128128

129129
[tool.pytest.ini_options]
130-
addopts = "--verbose --cov-config=pyproject.toml"
130+
addopts = "--verbose --cov-config=pyproject.toml --cov-report=html"
131131

132132
[tool.ruff]
133133
# ruff is less lenient than pylint and does not make any exceptions
@@ -230,4 +230,8 @@ output-format = "colorized"
230230
ignored-argument-names = "_.*"
231231

232232
[tool.pylint."messages control"]
233-
enable = ["useless-suppression"]
233+
enable = [
234+
"useless-suppression",
235+
"use-implicit-booleaness-not-comparison-to-zero",
236+
"use-implicit-booleaness-not-comparison-to-string",
237+
]

tests/base_tester.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,31 @@
11
import os
22
import sys
33
from abc import ABC
4+
from pathlib import Path
45
from pprint import pprint
5-
from typing import Any, Dict, List
6+
from typing import List, Type
67

78
import astroid
8-
from pylint.testutils import MessageTest, UnittestLinter
9-
10-
try:
11-
from pylint.utils import ASTWalker
12-
except ImportError:
13-
# for pylint 1.9
14-
from pylint.utils import PyLintASTWalker as ASTWalker
15-
169
from pylint.checkers import BaseChecker
10+
from pylint.testutils import MessageTest, UnittestLinter
11+
from pylint.utils import ASTWalker
1712

1813
import pylint_pytest.checkers.fixture
1914

2015
# XXX: allow all file names
2116
pylint_pytest.checkers.fixture.FILE_NAME_PATTERNS = ("*",)
2217

2318

19+
def get_test_root_path() -> Path:
20+
"""Assumes ``base_tester.py`` is at ``<root>/tests``."""
21+
return Path(__file__).parent
22+
23+
2424
class BasePytestTester(ABC):
2525
CHECKER_CLASS = BaseChecker
26-
IMPACTED_CHECKER_CLASSES: List[BaseChecker] = []
26+
IMPACTED_CHECKER_CLASSES: List[Type[BaseChecker]] = []
2727
MSG_ID: str
2828
msgs: List[MessageTest] = []
29-
CONFIG: Dict[str, Any] = {}
3029

3130
def __init_subclass__(cls, **kwargs):
3231
super().__init_subclass__(**kwargs)
@@ -41,7 +40,10 @@ def run_linter(self, enable_plugin):
4140
# pylint: disable-next=protected-access
4241
target_test_file = sys._getframe(1).f_code.co_name.replace("test_", "", 1)
4342
file_path = os.path.join(
44-
os.getcwd(), "tests", "input", self.MSG_ID, target_test_file + ".py"
43+
get_test_root_path(),
44+
"input",
45+
self.MSG_ID,
46+
target_test_file + ".py",
4547
)
4648

4749
with open(file_path) as fin:
@@ -69,8 +71,6 @@ def setup_method(self):
6971
self.checker = self.CHECKER_CLASS(self.linter)
7072
self.impacted_checkers = []
7173

72-
for key, value in self.CONFIG.items():
73-
setattr(self.linter.config, key, value)
7474
self.checker.open()
7575

7676
for checker_class in self.IMPACTED_CHECKER_CLASSES:

tests/base_tester_test.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import pytest
2-
from base_tester import BasePytestTester
2+
from base_tester import BasePytestTester, get_test_root_path
33

44
# pylint: disable=unused-variable
55

66

7+
def test_get_test_root_path():
8+
assert get_test_root_path().name == "tests"
9+
assert (get_test_root_path() / "input").is_dir()
10+
11+
712
def test_init_subclass_valid_msg_id():
813
some_string = "some_string"
914

0 commit comments

Comments
 (0)