mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-06 14:04:26 +08:00
Merge pull request #1879 from marciomazza/find-external-pytest-fixtures
Find external pytest fixtures
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
Main Authors
|
Main Authors
|
||||||
------------
|
------------
|
||||||
|
|
||||||
- David Halter (@davidhalter) <davidhalter88@gmail.com>
|
- David Halter (@davidhalter) <davidhalter88@gmail.com>
|
||||||
@@ -62,6 +62,7 @@ Code Contributors
|
|||||||
- Andrii Kolomoiets (@muffinmad)
|
- Andrii Kolomoiets (@muffinmad)
|
||||||
- Leo Ryu (@Leo-Ryu)
|
- Leo Ryu (@Leo-Ryu)
|
||||||
- Joseph Birkner (@josephbirkner)
|
- Joseph Birkner (@josephbirkner)
|
||||||
|
- Márcio Mazza (@marciomazza)
|
||||||
|
|
||||||
And a few more "anonymous" contributors.
|
And a few more "anonymous" contributors.
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
from parso.tree import search_ancestor
|
from parso.tree import search_ancestor
|
||||||
from jedi.inference.cache import inference_state_method_cache
|
from jedi.inference.cache import inference_state_method_cache
|
||||||
from jedi.inference.imports import load_module_from_path
|
from jedi.inference.imports import goto_import, load_module_from_path
|
||||||
from jedi.inference.filters import ParserTreeFilter
|
from jedi.inference.filters import ParserTreeFilter
|
||||||
from jedi.inference.base_value import NO_VALUES, ValueSet
|
from jedi.inference.base_value import NO_VALUES, ValueSet
|
||||||
from jedi.inference.helpers import infer_call_of_leaf
|
from jedi.inference.helpers import infer_call_of_leaf
|
||||||
@@ -131,6 +131,17 @@ def _is_pytest_func(func_name, decorator_nodes):
|
|||||||
or any('fixture' in n.get_code() for n in decorator_nodes)
|
or any('fixture' in n.get_code() for n in decorator_nodes)
|
||||||
|
|
||||||
|
|
||||||
|
def _find_pytest_plugin_modules():
|
||||||
|
"""
|
||||||
|
Finds pytest plugin modules hooked by setuptools entry points
|
||||||
|
|
||||||
|
See https://docs.pytest.org/en/stable/how-to/writing_plugins.html#setuptools-entry-points
|
||||||
|
"""
|
||||||
|
from pkg_resources import iter_entry_points
|
||||||
|
|
||||||
|
return [ep.module_name.split(".") for ep in iter_entry_points(group="pytest11")]
|
||||||
|
|
||||||
|
|
||||||
@inference_state_method_cache()
|
@inference_state_method_cache()
|
||||||
def _iter_pytest_modules(module_context, skip_own_module=False):
|
def _iter_pytest_modules(module_context, skip_own_module=False):
|
||||||
if not skip_own_module:
|
if not skip_own_module:
|
||||||
@@ -159,7 +170,7 @@ def _iter_pytest_modules(module_context, skip_own_module=False):
|
|||||||
break
|
break
|
||||||
last_folder = folder # keep track of the last found parent name
|
last_folder = folder # keep track of the last found parent name
|
||||||
|
|
||||||
for names in _PYTEST_FIXTURE_MODULES:
|
for names in _PYTEST_FIXTURE_MODULES + _find_pytest_plugin_modules():
|
||||||
for module_value in module_context.inference_state.import_module(names):
|
for module_value in module_context.inference_state.import_module(names):
|
||||||
yield module_value.as_context()
|
yield module_value.as_context()
|
||||||
|
|
||||||
@@ -167,14 +178,28 @@ def _iter_pytest_modules(module_context, skip_own_module=False):
|
|||||||
class FixtureFilter(ParserTreeFilter):
|
class FixtureFilter(ParserTreeFilter):
|
||||||
def _filter(self, names):
|
def _filter(self, names):
|
||||||
for name in super()._filter(names):
|
for name in super()._filter(names):
|
||||||
funcdef = name.parent
|
# look for fixture definitions of imported names
|
||||||
# Class fixtures are not supported
|
if name.parent.type == "import_from":
|
||||||
if funcdef.type == 'funcdef':
|
imported_names = goto_import(self.parent_context, name)
|
||||||
decorated = funcdef.parent
|
if any(
|
||||||
if decorated.type == 'decorated' and self._is_fixture(decorated):
|
self._is_fixture(iname.parent_context, iname.tree_name)
|
||||||
|
for iname in imported_names
|
||||||
|
# discard imports of whole modules, that have no tree_name
|
||||||
|
if iname.tree_name
|
||||||
|
):
|
||||||
yield name
|
yield name
|
||||||
|
|
||||||
def _is_fixture(self, decorated):
|
elif self._is_fixture(self.parent_context, name):
|
||||||
|
yield name
|
||||||
|
|
||||||
|
def _is_fixture(self, context, name):
|
||||||
|
funcdef = name.parent
|
||||||
|
# Class fixtures are not supported
|
||||||
|
if funcdef.type != "funcdef":
|
||||||
|
return False
|
||||||
|
decorated = funcdef.parent
|
||||||
|
if decorated.type != "decorated":
|
||||||
|
return False
|
||||||
decorators = decorated.children[0]
|
decorators = decorated.children[0]
|
||||||
if decorators.type == 'decorators':
|
if decorators.type == 'decorators':
|
||||||
decorators = decorators.children
|
decorators = decorators.children
|
||||||
@@ -191,11 +216,12 @@ class FixtureFilter(ParserTreeFilter):
|
|||||||
last_leaf = last_trailer.get_last_leaf()
|
last_leaf = last_trailer.get_last_leaf()
|
||||||
if last_leaf == ')':
|
if last_leaf == ')':
|
||||||
values = infer_call_of_leaf(
|
values = infer_call_of_leaf(
|
||||||
self.parent_context, last_leaf, cut_own_trailer=True)
|
context, last_leaf, cut_own_trailer=True
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
values = self.parent_context.infer_node(dotted_name)
|
values = context.infer_node(dotted_name)
|
||||||
else:
|
else:
|
||||||
values = self.parent_context.infer_node(dotted_name)
|
values = context.infer_node(dotted_name)
|
||||||
for value in values:
|
for value in values:
|
||||||
if value.name.get_qualified_names(include_module_names=True) \
|
if value.name.get_qualified_names(include_module_names=True) \
|
||||||
== ('_pytest', 'fixtures', 'fixture'):
|
== ('_pytest', 'fixtures', 'fixture'):
|
||||||
|
|||||||
@@ -183,3 +183,28 @@ def with_annot() -> Generator[float, None, None]:
|
|||||||
def test_with_annot(inheritance_fixture, with_annot):
|
def test_with_annot(inheritance_fixture, with_annot):
|
||||||
#? float()
|
#? float()
|
||||||
with_annot
|
with_annot
|
||||||
|
|
||||||
|
# -----------------
|
||||||
|
# pytest external plugins
|
||||||
|
# -----------------
|
||||||
|
|
||||||
|
#? ['admin_user', 'admin_client']
|
||||||
|
def test_z(admin
|
||||||
|
|
||||||
|
#! 15 ['def admin_client']
|
||||||
|
def test_p(admin_client):
|
||||||
|
#? ['login', 'logout']
|
||||||
|
admin_client.log
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
@some_decorator
|
||||||
|
#? ['admin_user']
|
||||||
|
def bla(admin_u
|
||||||
|
return
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
@some_decorator
|
||||||
|
#! 12 ['def admin_user']
|
||||||
|
def bla(admin_user):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
from pytest import fixture
|
||||||
|
|
||||||
|
|
||||||
|
@fixture()
|
||||||
|
def admin_user():
|
||||||
|
pass
|
||||||
16
test/examples/pytest_plugin_package/pytest_plugin/plugin.py
Normal file
16
test/examples/pytest_plugin_package/pytest_plugin/plugin.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from .fixtures import admin_user # noqa
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def admin_client():
|
||||||
|
return Client()
|
||||||
|
|
||||||
|
|
||||||
|
class Client:
|
||||||
|
def login(self, **credentials):
|
||||||
|
...
|
||||||
|
|
||||||
|
def logout(self):
|
||||||
|
...
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -42,6 +43,22 @@ def test_completion(case, monkeypatch, environment, has_django):
|
|||||||
|
|
||||||
if (not has_django) and case.path.endswith('django.py'):
|
if (not has_django) and case.path.endswith('django.py'):
|
||||||
pytest.skip('Needs django to be installed to run this test.')
|
pytest.skip('Needs django to be installed to run this test.')
|
||||||
|
|
||||||
|
if case.path.endswith("pytest.py"):
|
||||||
|
# to test finding pytest fixtures from external plugins
|
||||||
|
# add a stub pytest plugin to the project sys_path...
|
||||||
|
pytest_plugin_dir = str(helpers.get_example_dir("pytest_plugin_package"))
|
||||||
|
case._project.added_sys_path = [pytest_plugin_dir]
|
||||||
|
|
||||||
|
# ... and mock setuptools entry points to include it
|
||||||
|
# see https://docs.pytest.org/en/stable/how-to/writing_plugins.html#setuptools-entry-points
|
||||||
|
def mock_iter_entry_points(group):
|
||||||
|
assert group == "pytest11"
|
||||||
|
EntryPoint = namedtuple("EntryPoint", ["module_name"])
|
||||||
|
return [EntryPoint("pytest_plugin.plugin")]
|
||||||
|
|
||||||
|
monkeypatch.setattr("pkg_resources.iter_entry_points", mock_iter_entry_points)
|
||||||
|
|
||||||
repo_root = helpers.root_dir
|
repo_root = helpers.root_dir
|
||||||
monkeypatch.chdir(os.path.join(repo_root, 'jedi'))
|
monkeypatch.chdir(os.path.join(repo_root, 'jedi'))
|
||||||
case.run(assert_case_equal, environment)
|
case.run(assert_case_equal, environment)
|
||||||
|
|||||||
Reference in New Issue
Block a user