pytest.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. import sys
  2. from typing import List
  3. from pathlib import Path
  4. from parso.tree import search_ancestor
  5. from jedi.inference.cache import inference_state_method_cache
  6. from jedi.inference.imports import goto_import, load_module_from_path
  7. from jedi.inference.filters import ParserTreeFilter
  8. from jedi.inference.base_value import NO_VALUES, ValueSet
  9. from jedi.inference.helpers import infer_call_of_leaf
  10. _PYTEST_FIXTURE_MODULES = [
  11. ('_pytest', 'monkeypatch'),
  12. ('_pytest', 'capture'),
  13. ('_pytest', 'logging'),
  14. ('_pytest', 'tmpdir'),
  15. ('_pytest', 'pytester'),
  16. ]
  17. def execute(callback):
  18. def wrapper(value, arguments):
  19. # This might not be necessary anymore in pytest 4/5, definitely needed
  20. # for pytest 3.
  21. if value.py__name__() == 'fixture' \
  22. and value.parent_context.py__name__() == '_pytest.fixtures':
  23. return NO_VALUES
  24. return callback(value, arguments)
  25. return wrapper
  26. def infer_anonymous_param(func):
  27. def get_returns(value):
  28. if value.tree_node.annotation is not None:
  29. result = value.execute_with_values()
  30. if any(v.name.get_qualified_names(include_module_names=True)
  31. == ('typing', 'Generator')
  32. for v in result):
  33. return ValueSet.from_sets(
  34. v.py__getattribute__('__next__').execute_annotation()
  35. for v in result
  36. )
  37. return result
  38. # In pytest we need to differentiate between generators and normal
  39. # returns.
  40. # Parameters still need to be anonymous, .as_context() ensures that.
  41. function_context = value.as_context()
  42. if function_context.is_generator():
  43. return function_context.merge_yield_values()
  44. else:
  45. return function_context.get_return_values()
  46. def wrapper(param_name):
  47. # parameters with an annotation do not need special handling
  48. if param_name.annotation_node:
  49. return func(param_name)
  50. is_pytest_param, param_name_is_function_name = \
  51. _is_a_pytest_param_and_inherited(param_name)
  52. if is_pytest_param:
  53. module = param_name.get_root_context()
  54. fixtures = _goto_pytest_fixture(
  55. module,
  56. param_name.string_name,
  57. # This skips the current module, because we are basically
  58. # inheriting a fixture from somewhere else.
  59. skip_own_module=param_name_is_function_name,
  60. )
  61. if fixtures:
  62. return ValueSet.from_sets(
  63. get_returns(value)
  64. for fixture in fixtures
  65. for value in fixture.infer()
  66. )
  67. return func(param_name)
  68. return wrapper
  69. def goto_anonymous_param(func):
  70. def wrapper(param_name):
  71. is_pytest_param, param_name_is_function_name = \
  72. _is_a_pytest_param_and_inherited(param_name)
  73. if is_pytest_param:
  74. names = _goto_pytest_fixture(
  75. param_name.get_root_context(),
  76. param_name.string_name,
  77. skip_own_module=param_name_is_function_name,
  78. )
  79. if names:
  80. return names
  81. return func(param_name)
  82. return wrapper
  83. def complete_param_names(func):
  84. def wrapper(context, func_name, decorator_nodes):
  85. module_context = context.get_root_context()
  86. if _is_pytest_func(func_name, decorator_nodes):
  87. names = []
  88. for module_context in _iter_pytest_modules(module_context):
  89. names += FixtureFilter(module_context).values()
  90. if names:
  91. return names
  92. return func(context, func_name, decorator_nodes)
  93. return wrapper
  94. def _goto_pytest_fixture(module_context, name, skip_own_module):
  95. for module_context in _iter_pytest_modules(module_context, skip_own_module=skip_own_module):
  96. names = FixtureFilter(module_context).get(name)
  97. if names:
  98. return names
  99. def _is_a_pytest_param_and_inherited(param_name):
  100. """
  101. Pytest params are either in a `test_*` function or have a pytest fixture
  102. with the decorator @pytest.fixture.
  103. This is a heuristic and will work in most cases.
  104. """
  105. funcdef = search_ancestor(param_name.tree_name, 'funcdef')
  106. if funcdef is None: # A lambda
  107. return False, False
  108. decorators = funcdef.get_decorators()
  109. return _is_pytest_func(funcdef.name.value, decorators), \
  110. funcdef.name.value == param_name.string_name
  111. def _is_pytest_func(func_name, decorator_nodes):
  112. return func_name.startswith('test') \
  113. or any('fixture' in n.get_code() for n in decorator_nodes)
  114. def _find_pytest_plugin_modules() -> List[List[str]]:
  115. """
  116. Finds pytest plugin modules hooked by setuptools entry points
  117. See https://docs.pytest.org/en/stable/how-to/writing_plugins.html#setuptools-entry-points
  118. """
  119. if sys.version_info >= (3, 8):
  120. from importlib.metadata import entry_points
  121. if sys.version_info >= (3, 10):
  122. pytest_entry_points = entry_points(group="pytest11")
  123. else:
  124. pytest_entry_points = entry_points().get("pytest11", ())
  125. if sys.version_info >= (3, 9):
  126. return [ep.module.split(".") for ep in pytest_entry_points]
  127. else:
  128. # Python 3.8 doesn't have `EntryPoint.module`. Implement equivalent
  129. # to what Python 3.9 does (with additional None check to placate `mypy`)
  130. matches = [
  131. ep.pattern.match(ep.value)
  132. for ep in pytest_entry_points
  133. ]
  134. return [x.group('module').split(".") for x in matches if x]
  135. else:
  136. from pkg_resources import iter_entry_points
  137. return [ep.module_name.split(".") for ep in iter_entry_points(group="pytest11")]
  138. @inference_state_method_cache()
  139. def _iter_pytest_modules(module_context, skip_own_module=False):
  140. if not skip_own_module:
  141. yield module_context
  142. file_io = module_context.get_value().file_io
  143. if file_io is not None:
  144. folder = file_io.get_parent_folder()
  145. sys_path = module_context.inference_state.get_sys_path()
  146. # prevent an infinite loop when reaching the root of the current drive
  147. last_folder = None
  148. while any(folder.path.startswith(p) for p in sys_path):
  149. file_io = folder.get_file_io('conftest.py')
  150. if Path(file_io.path) != module_context.py__file__():
  151. try:
  152. m = load_module_from_path(module_context.inference_state, file_io)
  153. conftest_module = m.as_context()
  154. yield conftest_module
  155. plugins_list = m.tree_node.get_used_names().get("pytest_plugins")
  156. if plugins_list:
  157. name = conftest_module.create_name(plugins_list[0])
  158. yield from _load_pytest_plugins(module_context, name)
  159. except FileNotFoundError:
  160. pass
  161. folder = folder.get_parent_folder()
  162. # prevent an infinite for loop if the same parent folder is return twice
  163. if last_folder is not None and folder.path == last_folder.path:
  164. break
  165. last_folder = folder # keep track of the last found parent name
  166. for names in _PYTEST_FIXTURE_MODULES + _find_pytest_plugin_modules():
  167. for module_value in module_context.inference_state.import_module(names):
  168. yield module_value.as_context()
  169. def _load_pytest_plugins(module_context, name):
  170. from jedi.inference.helpers import get_str_or_none
  171. for inferred in name.infer():
  172. for seq_value in inferred.py__iter__():
  173. for value in seq_value.infer():
  174. fq_name = get_str_or_none(value)
  175. if fq_name:
  176. names = fq_name.split(".")
  177. for module_value in module_context.inference_state.import_module(names):
  178. yield module_value.as_context()
  179. class FixtureFilter(ParserTreeFilter):
  180. def _filter(self, names):
  181. for name in super()._filter(names):
  182. # look for fixture definitions of imported names
  183. if name.parent.type == "import_from":
  184. imported_names = goto_import(self.parent_context, name)
  185. if any(
  186. self._is_fixture(iname.parent_context, iname.tree_name)
  187. for iname in imported_names
  188. # discard imports of whole modules, that have no tree_name
  189. if iname.tree_name
  190. ):
  191. yield name
  192. elif self._is_fixture(self.parent_context, name):
  193. yield name
  194. def _is_fixture(self, context, name):
  195. funcdef = name.parent
  196. # Class fixtures are not supported
  197. if funcdef.type != "funcdef":
  198. return False
  199. decorated = funcdef.parent
  200. if decorated.type != "decorated":
  201. return False
  202. decorators = decorated.children[0]
  203. if decorators.type == 'decorators':
  204. decorators = decorators.children
  205. else:
  206. decorators = [decorators]
  207. for decorator in decorators:
  208. dotted_name = decorator.children[1]
  209. # A heuristic, this makes it faster.
  210. if 'fixture' in dotted_name.get_code():
  211. if dotted_name.type == 'atom_expr':
  212. # Since Python3.9 a decorator does not have dotted names
  213. # anymore.
  214. last_trailer = dotted_name.children[-1]
  215. last_leaf = last_trailer.get_last_leaf()
  216. if last_leaf == ')':
  217. values = infer_call_of_leaf(
  218. context, last_leaf, cut_own_trailer=True
  219. )
  220. else:
  221. values = context.infer_node(dotted_name)
  222. else:
  223. values = context.infer_node(dotted_name)
  224. for value in values:
  225. if value.name.get_qualified_names(include_module_names=True) \
  226. == ('_pytest', 'fixtures', 'fixture'):
  227. return True
  228. return False