diff --git a/pylint_pytest/utils.py b/pylint_pytest/utils.py index 6c66e6e..c9fa02f 100644 --- a/pylint_pytest/utils.py +++ b/pylint_pytest/utils.py @@ -31,7 +31,6 @@ def _is_pytest_mark(decorator): def _is_pytest_fixture(decorator, fixture=True, yield_fixture=True): - attr = None to_check = set() if fixture: @@ -40,17 +39,38 @@ def _is_pytest_fixture(decorator, fixture=True, yield_fixture=True): if yield_fixture: to_check.add("yield_fixture") + def _check_attribute(attr): + """ + handle astroid.Attribute, i.e., when the fixture function is + used by importing the pytest module + """ + return attr.attrname in to_check and attr.expr.name == "pytest" + + def _check_name(name_): + """ + handle astroid.Name, i.e., when the fixture function is + directly imported + """ + function_name = name_.name + module = decorator.root().globals.get(function_name, [None])[0] + module_name = module.modname if module else None + return function_name in to_check and module_name == "pytest" + try: + if isinstance(decorator, astroid.Name): + # expecting @fixture + return _check_name(decorator) if isinstance(decorator, astroid.Attribute): # expecting @pytest.fixture - attr = decorator - + return _check_attribute(decorator) if isinstance(decorator, astroid.Call): + func = decorator.func + if isinstance(func, astroid.Name): + # expecting @fixture(scope=...) + return _check_name(func) # expecting @pytest.fixture(scope=...) - attr = decorator.func + return _check_attribute(func) - if attr and attr.attrname in to_check and attr.expr.name == "pytest": - return True except AttributeError: pass diff --git a/tests/input/redefined-outer-name/direct_import.py b/tests/input/redefined-outer-name/direct_import.py new file mode 100644 index 0000000..d926094 --- /dev/null +++ b/tests/input/redefined-outer-name/direct_import.py @@ -0,0 +1,28 @@ +from pytest import fixture + + +@fixture +def simple_decorator(): + """the fixture using the decorator without executing it""" + + +def test_simple_fixture(simple_decorator): + assert True + + +@fixture() +def un_configured_decorated(): + """the decorated is called without argument, like scope""" + + +def test_un_configured_decorated(un_configured_decorated): + assert True + + +@fixture(scope="function") +def configured_decorated(): + """the decorated is called with argument, like scope""" + + +def test_un_configured_decorated(configured_decorated): + assert True diff --git a/tests/test_redefined_outer_name.py b/tests/test_redefined_outer_name.py index 11e48a0..ec0beef 100644 --- a/tests/test_redefined_outer_name.py +++ b/tests/test_redefined_outer_name.py @@ -29,3 +29,9 @@ def test_caller_not_a_test_func(self, enable_plugin): def test_args_and_kwargs(self, enable_plugin): self.run_linter(enable_plugin) self.verify_messages(2) + + @pytest.mark.parametrize("enable_plugin", [True, False]) + def test_direct_import(self, enable_plugin): + """the fixture method is directly imported""" + self.run_linter(enable_plugin) + self.verify_messages(0 if enable_plugin else 3)