-
-
Notifications
You must be signed in to change notification settings - Fork 508
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'pytest', fixes parts of #791
- Loading branch information
Showing
18 changed files
with
410 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
from parso.python.tree import search_ancestor | ||
from jedi._compatibility import FileNotFoundError | ||
from jedi.inference.cache import inference_state_method_cache | ||
from jedi.inference.imports import load_module_from_path | ||
from jedi.inference.filters import ParserTreeFilter | ||
from jedi.inference.base_value import NO_VALUES, ValueSet | ||
|
||
|
||
def execute(callback): | ||
def wrapper(value, arguments): | ||
# This might not be necessary anymore in pytest 4/5, definitely needed | ||
# for pytest 3. | ||
if value.py__name__() == 'fixture' \ | ||
and value.parent_context.py__name__() == '_pytest.fixtures': | ||
return NO_VALUES | ||
|
||
return callback(value, arguments) | ||
return wrapper | ||
|
||
|
||
def infer_anonymous_param(func): | ||
def get_returns(value): | ||
if value.tree_node.annotation is not None: | ||
return value.execute_with_values() | ||
|
||
# In pytest we need to differentiate between generators and normal | ||
# returns. | ||
# Parameters still need to be anonymous, .as_context() ensures that. | ||
function_context = value.as_context() | ||
if function_context.is_generator(): | ||
return function_context.merge_yield_values() | ||
else: | ||
return function_context.get_return_values() | ||
|
||
def wrapper(param_name): | ||
if _is_a_pytest_param(param_name): | ||
module = param_name.get_root_context() | ||
fixtures = _goto_pytest_fixture(module, param_name.string_name) | ||
if fixtures: | ||
return ValueSet.from_sets( | ||
get_returns(value) | ||
for fixture in fixtures | ||
for value in fixture.infer() | ||
) | ||
return func(param_name) | ||
return wrapper | ||
|
||
|
||
def goto_anonymous_param(func): | ||
def wrapper(param_name): | ||
if _is_a_pytest_param(param_name): | ||
names = _goto_pytest_fixture(param_name.get_root_context(), param_name.string_name) | ||
if names: | ||
return names | ||
return func(param_name) | ||
return wrapper | ||
|
||
|
||
def complete_param_names(func): | ||
def wrapper(context, func_name, decorator_nodes): | ||
module_context = context.get_root_context() | ||
if _is_pytest_func(func_name, decorator_nodes): | ||
names = [] | ||
for module_context in _iter_pytest_modules(module_context): | ||
names += FixtureFilter(module_context).values() | ||
if names: | ||
return names | ||
return func(context, func_name, decorator_nodes) | ||
return wrapper | ||
|
||
|
||
def _goto_pytest_fixture(module_context, name): | ||
for module_context in _iter_pytest_modules(module_context): | ||
names = FixtureFilter(module_context).get(name) | ||
if names: | ||
return names | ||
|
||
|
||
def _is_a_pytest_param(param_name): | ||
""" | ||
Pytest params are either in a `test_*` function or have a pytest fixture | ||
with the decorator @pytest.fixture. | ||
This is a heuristic and will work in most cases. | ||
""" | ||
funcdef = search_ancestor(param_name.tree_name, 'funcdef') | ||
if funcdef is None: # A lambda | ||
return False | ||
decorators = funcdef.get_decorators() | ||
return _is_pytest_func(funcdef.name.value, decorators) | ||
|
||
|
||
def _is_pytest_func(func_name, decorator_nodes): | ||
return func_name.startswith('test_') \ | ||
or any('fixture' in n.get_code() for n in decorator_nodes) | ||
|
||
|
||
@inference_state_method_cache() | ||
def _iter_pytest_modules(module_context): | ||
yield module_context | ||
|
||
folder = module_context.get_value().file_io.get_parent_folder() | ||
sys_path = module_context.inference_state.get_sys_path() | ||
while any(folder.path.startswith(p) for p in sys_path): | ||
file_io = folder.get_file_io('conftest.py') | ||
try: | ||
m = load_module_from_path(module_context.inference_state, file_io) | ||
yield m.as_context() | ||
except FileNotFoundError: | ||
pass | ||
folder = folder.get_parent_folder() | ||
|
||
|
||
class FixtureFilter(ParserTreeFilter): | ||
def _filter(self, names): | ||
for name in super(FixtureFilter, self)._filter(names): | ||
funcdef = name.parent | ||
if funcdef.type == 'funcdef': | ||
# Class fixtures are not supported | ||
decorated = funcdef.parent | ||
if decorated.type == 'decorated' and self._is_fixture(decorated): | ||
yield name | ||
|
||
def _is_fixture(self, decorated): | ||
for decorator in decorated.children: | ||
dotted_name = decorator.children[1] | ||
# A heuristic, this makes it faster. | ||
if 'fixture' in dotted_name.get_code(): | ||
for value in self.parent_context.infer_node(dotted_name): | ||
if value.name.get_qualified_names(include_module_names=True) \ | ||
== ('_pytest', 'fixtures', 'fixture'): | ||
return True | ||
return False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.