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

130 statements  

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

1from pathlib import Path 

2 

3from parso.tree import search_ancestor 

4from jedi.inference.cache import inference_state_method_cache 

5from jedi.inference.imports import goto_import, load_module_from_path 

6from jedi.inference.filters import ParserTreeFilter 

7from jedi.inference.base_value import NO_VALUES, ValueSet 

8from jedi.inference.helpers import infer_call_of_leaf 

9 

10_PYTEST_FIXTURE_MODULES = [ 

11 ('_pytest', 'monkeypatch'), 

12 ('_pytest', 'capture'), 

13 ('_pytest', 'logging'), 

14 ('_pytest', 'tmpdir'), 

15 ('_pytest', 'pytester'), 

16] 

17 

18 

19def execute(callback): 

20 def wrapper(value, arguments): 

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

22 # for pytest 3. 

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

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

25 return NO_VALUES 

26 

27 return callback(value, arguments) 

28 return wrapper 

29 

30 

31def infer_anonymous_param(func): 

32 def get_returns(value): 

33 if value.tree_node.annotation is not None: 

34 result = value.execute_with_values() 

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

36 == ('typing', 'Generator') 

37 for v in result): 

38 return ValueSet.from_sets( 

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

40 for v in result 

41 ) 

42 return result 

43 

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

45 # returns. 

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

47 function_context = value.as_context() 

48 if function_context.is_generator(): 

49 return function_context.merge_yield_values() 

50 else: 

51 return function_context.get_return_values() 

52 

53 def wrapper(param_name): 

54 # parameters with an annotation do not need special handling 

55 if param_name.annotation_node: 

56 return func(param_name) 

57 is_pytest_param, param_name_is_function_name = \ 

58 _is_a_pytest_param_and_inherited(param_name) 

59 if is_pytest_param: 

60 module = param_name.get_root_context() 

61 fixtures = _goto_pytest_fixture( 

62 module, 

63 param_name.string_name, 

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

65 # inheriting a fixture from somewhere else. 

66 skip_own_module=param_name_is_function_name, 

67 ) 

68 if fixtures: 

69 return ValueSet.from_sets( 

70 get_returns(value) 

71 for fixture in fixtures 

72 for value in fixture.infer() 

73 ) 

74 return func(param_name) 

75 return wrapper 

76 

77 

78def goto_anonymous_param(func): 

79 def wrapper(param_name): 

80 is_pytest_param, param_name_is_function_name = \ 

81 _is_a_pytest_param_and_inherited(param_name) 

82 if is_pytest_param: 

83 names = _goto_pytest_fixture( 

84 param_name.get_root_context(), 

85 param_name.string_name, 

86 skip_own_module=param_name_is_function_name, 

87 ) 

88 if names: 

89 return names 

90 return func(param_name) 

91 return wrapper 

92 

93 

94def complete_param_names(func): 

95 def wrapper(context, func_name, decorator_nodes): 

96 module_context = context.get_root_context() 

97 if _is_pytest_func(func_name, decorator_nodes): 

98 names = [] 

99 for module_context in _iter_pytest_modules(module_context): 

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

101 if names: 

102 return names 

103 return func(context, func_name, decorator_nodes) 

104 return wrapper 

105 

106 

107def _goto_pytest_fixture(module_context, name, skip_own_module): 

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

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

110 if names: 

111 return names 

112 

113 

114def _is_a_pytest_param_and_inherited(param_name): 

115 """ 

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

117 with the decorator @pytest.fixture. 

118 

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

120 """ 

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

122 if funcdef is None: # A lambda 

123 return False, False 

124 decorators = funcdef.get_decorators() 

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

126 funcdef.name.value == param_name.string_name 

127 

128 

129def _is_pytest_func(func_name, decorator_nodes): 

130 return func_name.startswith('test') \ 

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

132 

133 

134def _find_pytest_plugin_modules(): 

135 """ 

136 Finds pytest plugin modules hooked by setuptools entry points 

137 

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

139 """ 

140 from pkg_resources import iter_entry_points 

141 

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

143 

144 

145@inference_state_method_cache() 

146def _iter_pytest_modules(module_context, skip_own_module=False): 

147 if not skip_own_module: 

148 yield module_context 

149 

150 file_io = module_context.get_value().file_io 

151 if file_io is not None: 

152 folder = file_io.get_parent_folder() 

153 sys_path = module_context.inference_state.get_sys_path() 

154 

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

156 last_folder = None 

157 

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

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

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

161 try: 

162 m = load_module_from_path(module_context.inference_state, file_io) 

163 yield m.as_context() 

164 except FileNotFoundError: 

165 pass 

166 folder = folder.get_parent_folder() 

167 

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

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

170 break 

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

172 

173 for names in _PYTEST_FIXTURE_MODULES + _find_pytest_plugin_modules(): 

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

175 yield module_value.as_context() 

176 

177 

178class FixtureFilter(ParserTreeFilter): 

179 def _filter(self, names): 

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

181 # look for fixture definitions of imported names 

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

183 imported_names = goto_import(self.parent_context, name) 

184 if any( 

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

186 for iname in imported_names 

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

188 if iname.tree_name 

189 ): 

190 yield name 

191 

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

193 yield name 

194 

195 def _is_fixture(self, context, name): 

196 funcdef = name.parent 

197 # Class fixtures are not supported 

198 if funcdef.type != "funcdef": 

199 return False 

200 decorated = funcdef.parent 

201 if decorated.type != "decorated": 

202 return False 

203 decorators = decorated.children[0] 

204 if decorators.type == 'decorators': 

205 decorators = decorators.children 

206 else: 

207 decorators = [decorators] 

208 for decorator in decorators: 

209 dotted_name = decorator.children[1] 

210 # A heuristic, this makes it faster. 

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

212 if dotted_name.type == 'atom_expr': 

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

214 # anymore. 

215 last_trailer = dotted_name.children[-1] 

216 last_leaf = last_trailer.get_last_leaf() 

217 if last_leaf == ')': 

218 values = infer_call_of_leaf( 

219 context, last_leaf, cut_own_trailer=True 

220 ) 

221 else: 

222 values = context.infer_node(dotted_name) 

223 else: 

224 values = context.infer_node(dotted_name) 

225 for value in values: 

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

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

228 return True 

229 return False