Skip to content

Commit 22f2047

Browse files
committed
add W6401 deprecated-pytest-yield-fixture
1 parent fa82023 commit 22f2047

File tree

11 files changed

+100
-34
lines changed

11 files changed

+100
-34
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Changelog
22

33
## [Unreleased]
4+
### Added
5+
- W6401 `deprecated-pytest-yield-fixture`: add warning for [yield_fixture functions](https://docs.pytest.org/en/latest/yieldfixture.html)
46

57
## [0.2.0] - 2020-05-25
68
### Added

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,20 @@ class TestClass(object):
8484
assert self.defined_in_setup_class # <- Instance of 'TestClass' has no 'defined_in_setup_class' member
8585
```
8686

87+
## Raise new warning(s)
88+
89+
### W6401 `deprecated-pytest-yield-fixture`
90+
91+
Raise when using deprecated `@pytest.yield_fixture` decorator ([ref](https://docs.pytest.org/en/latest/yieldfixture.html))
92+
93+
```python
94+
import pytest
95+
96+
@pytest.yield_fixture # <- Using a deprecated @pytest.yield_fixture decorator
97+
def yield_fixture():
98+
yield
99+
```
100+
87101
## Changelog
88102

89103
See [CHANGELOG](CHANGELOG.md).

pylint_pytest/checkers/fixture_loader.py renamed to pylint_pytest/checkers/fixture.py

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from pylint.checkers.variables import VariablesChecker
66
from pylint.interfaces import IAstroidChecker
77
import pytest
8-
from ..utils import _can_use_fixture, _is_same_module, _is_pytest_mark_usefixtures
8+
from ..utils import _can_use_fixture, _is_same_module, _is_pytest_mark_usefixtures, _is_pytest_fixture
99
from . import BasePytestChecker
1010

1111

@@ -17,9 +17,15 @@ def pytest_sessionfinish(self, session):
1717
self.fixtures = session._fixturemanager._arg2fixturedefs
1818

1919

20-
class FixtureLoader(BasePytestChecker):
20+
class FixtureChecker(BasePytestChecker):
2121
__implements__ = IAstroidChecker
22-
msgs = {'E6401': ('', 'pytest-fixture-loader', '')}
22+
msgs = {
23+
'W6401': (
24+
'Using a deprecated @pytest.yield_fixture decorator',
25+
'deprecated-pytest-yield-fixture',
26+
'Used when using a deprecated pytest decorator that has been deprecated in pytest-3.0'
27+
),
28+
}
2329

2430
_pytest_fixtures = {}
2531
_invoked_with_func_args = set()
@@ -28,19 +34,19 @@ class FixtureLoader(BasePytestChecker):
2834

2935
def open(self):
3036
# patch VariablesChecker.add_message
31-
FixtureLoader._original_add_message = VariablesChecker.add_message
32-
VariablesChecker.add_message = FixtureLoader.patch_add_message
37+
FixtureChecker._original_add_message = VariablesChecker.add_message
38+
VariablesChecker.add_message = FixtureChecker.patch_add_message
3339

3440
def close(self):
3541
'''restore & reset class attr for testing'''
3642
# restore add_message
37-
VariablesChecker.add_message = FixtureLoader._original_add_message
38-
FixtureLoader._original_add_message = callable
43+
VariablesChecker.add_message = FixtureChecker._original_add_message
44+
FixtureChecker._original_add_message = callable
3945

4046
# reset fixture info storage
41-
FixtureLoader._pytest_fixtures = {}
42-
FixtureLoader._invoked_with_func_args = set()
43-
FixtureLoader._invoked_with_usefixtures = set()
47+
FixtureChecker._pytest_fixtures = {}
48+
FixtureChecker._invoked_with_func_args = set()
49+
FixtureChecker._invoked_with_usefixtures = set()
4450

4551
def visit_module(self, node):
4652
'''
@@ -49,13 +55,13 @@ def visit_module(self, node):
4955
- create containers for the module to store args and fixtures
5056
'''
5157
# storing all fixtures discovered by pytest session
52-
FixtureLoader._pytest_fixtures = {} # Dict[List[_pytest.fixtures.FixtureDef]]
58+
FixtureChecker._pytest_fixtures = {} # Dict[List[_pytest.fixtures.FixtureDef]]
5359

5460
# storing all used function arguments
55-
FixtureLoader._invoked_with_func_args = set() # Set[str]
61+
FixtureChecker._invoked_with_func_args = set() # Set[str]
5662

5763
# storing all invoked fixtures through @pytest.mark.usefixture(...)
58-
FixtureLoader._invoked_with_usefixtures = set() # Set[str]
64+
FixtureChecker._invoked_with_usefixtures = set() # Set[str]
5965

6066
try:
6167
with open(os.devnull, 'w') as devnull:
@@ -69,7 +75,7 @@ def visit_module(self, node):
6975
[node.file, '--fixtures'],
7076
plugins=[fixture_collector],
7177
)
72-
FixtureLoader._pytest_fixtures = fixture_collector.fixtures
78+
FixtureChecker._pytest_fixtures = fixture_collector.fixtures
7379
finally:
7480
# restore output devices
7581
sys.stdout, sys.stderr = stdout, stderr
@@ -87,6 +93,10 @@ def visit_functiondef(self, node):
8793
# save all visited fixtures
8894
for arg in decorator.args:
8995
self._invoked_with_usefixtures.add(arg.value)
96+
if int(pytest.__version__.split('.')[0]) >= 3 and \
97+
_is_pytest_fixture(decorator, fixture=False):
98+
# raise deprecated warning for @pytest.yield_fixture
99+
self.add_message('deprecated-pytest-yield-fixture', node=node)
90100
for arg in node.args.args:
91101
self._invoked_with_func_args.add(arg.name)
92102

@@ -109,17 +119,17 @@ def patch_add_message(self, msgid, line=None, node=None, args=None,
109119
pass
110120

111121
# imported fixture is referenced in test/fixture func
112-
elif fixture_name in FixtureLoader._invoked_with_func_args \
113-
and fixture_name in FixtureLoader._pytest_fixtures:
114-
if _is_same_module(fixtures=FixtureLoader._pytest_fixtures,
122+
elif fixture_name in FixtureChecker._invoked_with_func_args \
123+
and fixture_name in FixtureChecker._pytest_fixtures:
124+
if _is_same_module(fixtures=FixtureChecker._pytest_fixtures,
115125
import_node=node,
116126
fixture_name=fixture_name):
117127
return
118128

119129
# fixture is referenced in @pytest.mark.usefixtures
120-
elif fixture_name in FixtureLoader._invoked_with_usefixtures \
121-
and fixture_name in FixtureLoader._pytest_fixtures:
122-
if _is_same_module(fixtures=FixtureLoader._pytest_fixtures,
130+
elif fixture_name in FixtureChecker._invoked_with_usefixtures \
131+
and fixture_name in FixtureChecker._pytest_fixtures:
132+
if _is_same_module(fixtures=FixtureChecker._pytest_fixtures,
123133
import_node=node,
124134
fixture_name=fixture_name):
125135
return
@@ -128,20 +138,20 @@ def patch_add_message(self, msgid, line=None, node=None, args=None,
128138
if msgid == 'unused-argument' and \
129139
_can_use_fixture(node.parent.parent) and \
130140
isinstance(node.parent, astroid.Arguments) and \
131-
node.name in FixtureLoader._pytest_fixtures:
141+
node.name in FixtureChecker._pytest_fixtures:
132142
return
133143

134144
# check W0621 redefined-outer-name
135145
if msgid == 'redefined-outer-name' and \
136146
_can_use_fixture(node.parent.parent) and \
137147
isinstance(node.parent, astroid.Arguments) and \
138-
node.name in FixtureLoader._pytest_fixtures:
148+
node.name in FixtureChecker._pytest_fixtures:
139149
return
140150

141151
if int(pylint.__version__.split('.')[0]) >= 2:
142-
FixtureLoader._original_add_message(
152+
FixtureChecker._original_add_message(
143153
self, msgid, line, node, args, confidence, col_offset)
144154
else:
145155
# python2 + pylint1.9 backward compatibility
146-
FixtureLoader._original_add_message(
156+
FixtureChecker._original_add_message(
147157
self, msgid, line, node, args, confidence)

pylint_pytest/utils.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,15 @@ def _is_pytest_mark_usefixtures(decorator):
1515
return False
1616

1717

18-
def _is_pytest_fixture(decorator):
18+
def _is_pytest_fixture(decorator, fixture=True, yield_fixture=True):
1919
attr = None
20+
to_check = set()
21+
22+
if fixture:
23+
to_check.add('fixture')
24+
25+
if yield_fixture:
26+
to_check.add('yield_fixture')
2027

2128
try:
2229
if isinstance(decorator, astroid.Attribute):
@@ -27,7 +34,7 @@ def _is_pytest_fixture(decorator):
2734
# expecting @pytest.fixture(scope=...)
2835
attr = decorator.func
2936

30-
if attr and attr.attrname in ('fixture', 'yield_fixture') \
37+
if attr and attr.attrname in to_check \
3138
and attr.expr.name == 'pytest':
3239
return True
3340
except AttributeError:
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import pytest
2+
3+
4+
@pytest.yield_fixture()
5+
def yield_fixture():
6+
yield
7+
8+
9+
@pytest.yield_fixture(scope='session')
10+
def yield_fixture_session():
11+
yield
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import pytest
2+
3+
4+
@pytest.yield_fixture
5+
def yield_fixture():
6+
yield

tests/test_pytest_yield_fixture.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from base_tester import BasePytestTester
2+
from pylint_pytest.checkers.fixture import FixtureChecker
3+
4+
5+
class TestDeprecatedPytestYieldFixture(BasePytestTester):
6+
CHECKER_CLASS = FixtureChecker
7+
IMPACTED_CHECKER_CLASSES = []
8+
MSG_ID = 'deprecated-pytest-yield-fixture'
9+
10+
def test_smoke(self):
11+
self.run_linter(enable_plugin=True)
12+
self.verify_messages(1)
13+
14+
def test_func(self):
15+
self.run_linter(enable_plugin=True)
16+
self.verify_messages(2)

tests/test_redefined_outer_name.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import pytest
22
from pylint.checkers.variables import VariablesChecker
33
from base_tester import BasePytestTester
4-
from pylint_pytest.checkers.fixture_loader import FixtureLoader
4+
from pylint_pytest.checkers.fixture import FixtureChecker
55

66

77
class TestRedefinedOuterName(BasePytestTester):
8-
CHECKER_CLASS = FixtureLoader
8+
CHECKER_CLASS = FixtureChecker
99
IMPACTED_CHECKER_CLASSES = [VariablesChecker]
1010
MSG_ID = 'redefined-outer-name'
1111

tests/test_regression.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
import pytest
33
from pylint.checkers.variables import VariablesChecker
44
from base_tester import BasePytestTester
5-
from pylint_pytest.checkers.fixture_loader import FixtureLoader
5+
from pylint_pytest.checkers.fixture import FixtureChecker
66

77

88
class TestRegression(BasePytestTester):
99
'''Covering some behaviors that shouldn't get impacted by the plugin'''
10-
CHECKER_CLASS = FixtureLoader
10+
CHECKER_CLASS = FixtureChecker
1111
IMPACTED_CHECKER_CLASSES = [VariablesChecker]
1212
MSG_ID = 'regression'
1313

tests/test_unused_argument.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import pytest
22
from pylint.checkers.variables import VariablesChecker
33
from base_tester import BasePytestTester
4-
from pylint_pytest.checkers.fixture_loader import FixtureLoader
4+
from pylint_pytest.checkers.fixture import FixtureChecker
55

66

77
class TestUnusedArgument(BasePytestTester):
8-
CHECKER_CLASS = FixtureLoader
8+
CHECKER_CLASS = FixtureChecker
99
IMPACTED_CHECKER_CLASSES = [VariablesChecker]
1010
MSG_ID = 'unused-argument'
1111

0 commit comments

Comments
 (0)