Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/jedi-0.18.2-py3.8.egg/jedi/plugins/pytest.py: 23%

141 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:56 +0000

1import sys 

2from typing import List 

3from pathlib import Path 

4 

5from parso.tree import search_ancestor 

6from jedi.inference.cache import inference_state_method_cache 

7from jedi.inference.imports import goto_import, load_module_from_path 

8from jedi.inference.filters import ParserTreeFilter 

9from jedi.inference.base_value import NO_VALUES, ValueSet 

10from jedi.inference.helpers import infer_call_of_leaf 

11 

12_PYTEST_FIXTURE_MODULES = [ 

13 ('_pytest', 'monkeypatch'), 

14 ('_pytest', 'capture'), 

15 ('_pytest', 'logging'), 

16 ('_pytest', 'tmpdir'), 

17 ('_pytest', 'pytester'), 

18] 

19 

20 

21def execute(callback): 

22 def wrapper(value, arguments): 

23 # This might not be necessary anymore in pytest 4/5, definitely needed 

24 # for pytest 3. 

25 if value.py__name__() == 'fixture' \ 

26 and value.parent_context.py__name__() == '_pytest.fixtures': 

27 return NO_VALUES 

28 

29 return callback(value, arguments) 

30 return wrapper 

31 

32 

33def infer_anonymous_param(func): 

34 def get_returns(value): 

35 if value.tree_node.annotation is not None: 

36 result = value.execute_with_values() 

37 if any(v.name.get_qualified_names(include_module_names=True) 

38 == ('typing', 'Generator') 

39 for v in result): 

40 return ValueSet.from_sets( 

41 v.py__getattribute__('__next__').execute_annotation() 

42 for v in result 

43 ) 

44 return result 

45 

46 # In pytest we need to differentiate between generators and normal 

47 # returns. 

48 # Parameters still need to be anonymous, .as_context() ensures that. 

49 function_context = value.as_context() 

50 if function_context.is_generator(): 

51 return function_context.merge_yield_values() 

52 else: 

53 return function_context.get_return_values() 

54 

55 def wrapper(param_name): 

56 # parameters with an annotation do not need special handling 

57 if param_name.annotation_node: 

58 return func(param_name) 

59 is_pytest_param, param_name_is_function_name = \ 

60 _is_a_pytest_param_and_inherited(param_name) 

61 if is_pytest_param: 

62 module = param_name.get_root_context() 

63 fixtures = _goto_pytest_fixture( 

64 module, 

65 param_name.string_name, 

66 # This skips the current module, because we are basically 

67 # inheriting a fixture from somewhere else. 

68 skip_own_module=param_name_is_function_name, 

69 ) 

70 if fixtures: 

71 return ValueSet.from_sets( 

72 get_returns(value) 

73 for fixture in fixtures 

74 for value in fixture.infer() 

75 ) 

76 return func(param_name) 

77 return wrapper 

78 

79 

80def goto_anonymous_param(func): 

81 def wrapper(param_name): 

82 is_pytest_param, param_name_is_function_name = \ 

83 _is_a_pytest_param_and_inherited(param_name) 

84 if is_pytest_param: 

85 names = _goto_pytest_fixture( 

86 param_name.get_root_context(), 

87 param_name.string_name, 

88 skip_own_module=param_name_is_function_name, 

89 ) 

90 if names: 

91 return names 

92 return func(param_name) 

93 return wrapper 

94 

95 

96def complete_param_names(func): 

97 def wrapper(context, func_name, decorator_nodes): 

98 module_context = context.get_root_context() 

99 if _is_pytest_func(func_name, decorator_nodes): 

100 names = [] 

101 for module_context in _iter_pytest_modules(module_context): 

102 names += FixtureFilter(module_context).values() 

103 if names: 

104 return names 

105 return func(context, func_name, decorator_nodes) 

106 return wrapper 

107 

108 

109def _goto_pytest_fixture(module_context, name, skip_own_module): 

110 for module_context in _iter_pytest_modules(module_context, skip_own_module=skip_own_module): 

111 names = FixtureFilter(module_context).get(name) 

112 if names: 

113 return names 

114 

115 

116def _is_a_pytest_param_and_inherited(param_name): 

117 """ 

118 Pytest params are either in a `test_*` function or have a pytest fixture 

119 with the decorator @pytest.fixture. 

120 

121 This is a heuristic and will work in most cases. 

122 """ 

123 funcdef = search_ancestor(param_name.tree_name, 'funcdef') 

124 if funcdef is None: # A lambda 

125 return False, False 

126 decorators = funcdef.get_decorators() 

127 return _is_pytest_func(funcdef.name.value, decorators), \ 

128 funcdef.name.value == param_name.string_name 

129 

130 

131def _is_pytest_func(func_name, decorator_nodes): 

132 return func_name.startswith('test') \ 

133 or any('fixture' in n.get_code() for n in decorator_nodes) 

134 

135 

136def _find_pytest_plugin_modules() -> List[List[str]]: 

137 """ 

138 Finds pytest plugin modules hooked by setuptools entry points 

139 

140 See https://docs.pytest.org/en/stable/how-to/writing_plugins.html#setuptools-entry-points 

141 """ 

142 if sys.version_info >= (3, 8): 

143 from importlib.metadata import entry_points 

144 

145 if sys.version_info >= (3, 10): 

146 pytest_entry_points = entry_points(group="pytest11") 

147 else: 

148 pytest_entry_points = entry_points().get("pytest11", ()) 

149 

150 if sys.version_info >= (3, 9): 

151 return [ep.module.split(".") for ep in pytest_entry_points] 

152 else: 

153 # Python 3.8 doesn't have `EntryPoint.module`. Implement equivalent 

154 # to what Python 3.9 does (with additional None check to placate `mypy`) 

155 matches = [ 

156 ep.pattern.match(ep.value) 

157 for ep in pytest_entry_points 

158 ] 

159 return [x.group('module').split(".") for x in matches if x] 

160 

161 else: 

162 from pkg_resources import iter_entry_points 

163 return [ep.module_name.split(".") for ep in iter_entry_points(group="pytest11")] 

164 

165 

166@inference_state_method_cache() 

167def _iter_pytest_modules(module_context, skip_own_module=False): 

168 if not skip_own_module: 

169 yield module_context 

170 

171 file_io = module_context.get_value().file_io 

172 if file_io is not None: 

173 folder = file_io.get_parent_folder() 

174 sys_path = module_context.inference_state.get_sys_path() 

175 

176 # prevent an infinite loop when reaching the root of the current drive 

177 last_folder = None 

178 

179 while any(folder.path.startswith(p) for p in sys_path): 

180 file_io = folder.get_file_io('conftest.py') 

181 if Path(file_io.path) != module_context.py__file__(): 

182 try: 

183 m = load_module_from_path(module_context.inference_state, file_io) 

184 yield m.as_context() 

185 except FileNotFoundError: 

186 pass 

187 folder = folder.get_parent_folder() 

188 

189 # prevent an infinite for loop if the same parent folder is return twice 

190 if last_folder is not None and folder.path == last_folder.path: 

191 break 

192 last_folder = folder # keep track of the last found parent name 

193 

194 for names in _PYTEST_FIXTURE_MODULES + _find_pytest_plugin_modules(): 

195 for module_value in module_context.inference_state.import_module(names): 

196 yield module_value.as_context() 

197 

198 

199class FixtureFilter(ParserTreeFilter): 

200 def _filter(self, names): 

201 for name in super()._filter(names): 

202 # look for fixture definitions of imported names 

203 if name.parent.type == "import_from": 

204 imported_names = goto_import(self.parent_context, name) 

205 if any( 

206 self._is_fixture(iname.parent_context, iname.tree_name) 

207 for iname in imported_names 

208 # discard imports of whole modules, that have no tree_name 

209 if iname.tree_name 

210 ): 

211 yield name 

212 

213 elif self._is_fixture(self.parent_context, name): 

214 yield name 

215 

216 def _is_fixture(self, context, name): 

217 funcdef = name.parent 

218 # Class fixtures are not supported 

219 if funcdef.type != "funcdef": 

220 return False 

221 decorated = funcdef.parent 

222 if decorated.type != "decorated": 

223 return False 

224 decorators = decorated.children[0] 

225 if decorators.type == 'decorators': 

226 decorators = decorators.children 

227 else: 

228 decorators = [decorators] 

229 for decorator in decorators: 

230 dotted_name = decorator.children[1] 

231 # A heuristic, this makes it faster. 

232 if 'fixture' in dotted_name.get_code(): 

233 if dotted_name.type == 'atom_expr': 

234 # Since Python3.9 a decorator does not have dotted names 

235 # anymore. 

236 last_trailer = dotted_name.children[-1] 

237 last_leaf = last_trailer.get_last_leaf() 

238 if last_leaf == ')': 

239 values = infer_call_of_leaf( 

240 context, last_leaf, cut_own_trailer=True 

241 ) 

242 else: 

243 values = context.infer_node(dotted_name) 

244 else: 

245 values = context.infer_node(dotted_name) 

246 for value in values: 

247 if value.name.get_qualified_names(include_module_names=True) \ 

248 == ('_pytest', 'fixtures', 'fixture'): 

249 return True 

250 return False