forked from PyCQA/pylint-pytest
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathutils.py
142 lines (114 loc) · 4.37 KB
/
utils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import inspect
import astroid
import pylint
PYLINT_VERSION_MAJOR = int(pylint.__version__.split(".")[0])
def _is_pytest_mark_usefixtures(decorator):
# expecting @pytest.mark.usefixture(...)
try:
if (
isinstance(decorator, astroid.Call)
and decorator.func.attrname == "usefixtures"
and decorator.func.expr.attrname == "mark"
and decorator.func.expr.expr.name == "pytest"
):
return True
except AttributeError:
pass
return False
def _is_pytest_mark(decorator):
try:
deco = decorator # as attribute `@pytest.mark.trylast`
if isinstance(decorator, astroid.Call):
deco = decorator.func # as function `@pytest.mark.skipif(...)`
if deco.expr.attrname == "mark" and deco.expr.expr.name == "pytest":
return True
except AttributeError:
pass
return False
def _is_pytest_fixture(decorator, fixture=True, yield_fixture=True):
to_check = set()
if fixture:
to_check.add("fixture")
if yield_fixture:
to_check.add("yield_fixture")
def _check_attribute(attr):
"""
handle astroid.Attribute, i.e., when the fixture function is
used by importing the pytest module
"""
return attr.attrname in to_check and attr.expr.name == "pytest"
def _check_name(name_):
"""
handle astroid.Name, i.e., when the fixture function is
directly imported
"""
function_name = name_.name
module = decorator.root().globals.get(function_name, [None])[0]
module_name = module.modname if module else None
return function_name in to_check and module_name == "pytest"
try:
if isinstance(decorator, astroid.Name):
# expecting @fixture
return _check_name(decorator)
if isinstance(decorator, astroid.Attribute):
# expecting @pytest.fixture
return _check_attribute(decorator)
if isinstance(decorator, astroid.Call):
func = decorator.func
if isinstance(func, astroid.Name):
# expecting @fixture(scope=...)
return _check_name(func)
# expecting @pytest.fixture(scope=...)
return _check_attribute(func)
except AttributeError:
pass
return False
def _is_class_autouse_fixture(function):
try:
for decorator in function.decorators.nodes:
if isinstance(decorator, astroid.Call):
func = decorator.func
if (
func
and func.attrname in ("fixture", "yield_fixture")
and func.expr.name == "pytest"
):
is_class = is_autouse = False
for kwarg in decorator.keywords or []:
if kwarg.arg == "scope" and kwarg.value.value == "class":
is_class = True
if kwarg.arg == "autouse" and kwarg.value.value is True:
is_autouse = True
if is_class and is_autouse:
return True
except AttributeError:
pass
return False
def _can_use_fixture(function):
if isinstance(function, astroid.FunctionDef):
# test_*, *_test
if function.name.startswith("test_") or function.name.endswith("_test"):
return True
if function.decorators:
for decorator in function.decorators.nodes:
# usefixture
if _is_pytest_mark_usefixtures(decorator):
return True
# fixture
if _is_pytest_fixture(decorator):
return True
return False
def _is_same_module(fixtures, import_node, fixture_name):
"""Comparing pytest fixture node with astroid.ImportFrom"""
try:
for fixture in fixtures[fixture_name]:
for import_from in import_node.root().globals[fixture_name]:
module = inspect.getmodule(fixture.func)
parent_import = import_from.parent.import_module(
import_from.modname, False, import_from.level
)
if module is not None and module.__file__ == parent_import.file:
return True
except Exception: # pylint: disable=broad-except
pass
return False