diff --git a/doc/whatsnew/fragments/9210.bugfix b/doc/whatsnew/fragments/9210.bugfix new file mode 100644 index 0000000000..c1d0835712 --- /dev/null +++ b/doc/whatsnew/fragments/9210.bugfix @@ -0,0 +1,4 @@ +Fix a bug where pylint was unable to walk recursively through a directory if the +directory has an `__init__.py` file. + +Closes #9210 diff --git a/pylint/lint/expand_modules.py b/pylint/lint/expand_modules.py index 1e8fd032f8..d42c798c9d 100644 --- a/pylint/lint/expand_modules.py +++ b/pylint/lint/expand_modules.py @@ -144,8 +144,9 @@ def expand_modules( ) if has_init or is_namespace or is_directory: for subfilepath in modutils.get_module_files( - os.path.dirname(filepath), ignore_list, list_all=is_namespace + os.path.dirname(filepath) or ".", ignore_list, list_all=is_namespace ): + subfilepath = os.path.normpath(subfilepath) if filepath == subfilepath: continue if _is_in_ignore_list_re( diff --git a/tests/lint/unittest_expand_modules.py b/tests/lint/unittest_expand_modules.py index 7120a17480..34133d759b 100644 --- a/tests/lint/unittest_expand_modules.py +++ b/tests/lint/unittest_expand_modules.py @@ -4,7 +4,11 @@ from __future__ import annotations +import copy +import os import re +from collections.abc import Iterator +from contextlib import contextmanager from pathlib import Path import pytest @@ -28,7 +32,8 @@ def test__is_in_ignore_list_re_match() -> None: TEST_DIRECTORY = Path(__file__).parent.parent INIT_PATH = str(TEST_DIRECTORY / "lint/__init__.py") -EXPAND_MODULES = str(TEST_DIRECTORY / "lint/unittest_expand_modules.py") +EXPAND_MODULES_BASE = "unittest_expand_modules.py" +EXPAND_MODULES = str(TEST_DIRECTORY / "lint" / EXPAND_MODULES_BASE) this_file = { "basename": "lint.unittest_expand_modules", "basepath": EXPAND_MODULES, @@ -37,6 +42,14 @@ def test__is_in_ignore_list_re_match() -> None: "path": EXPAND_MODULES, } +this_file_relative_to_parent = { + "basename": "lint.unittest_expand_modules", + "basepath": EXPAND_MODULES_BASE, + "isarg": True, + "name": "lint.unittest_expand_modules", + "path": EXPAND_MODULES_BASE, +} + this_file_from_init = { "basename": "lint", "basepath": INIT_PATH, @@ -117,6 +130,27 @@ def _list_expected_package_modules( ) +def _list_expected_package_modules_relative() -> tuple[dict[str, object], ...]: + """Generates reusable list of modules for our package with relative path input.""" + abs_result = copy.deepcopy(_list_expected_package_modules()) + for item in abs_result: + assert isinstance(item["basepath"], str) + assert isinstance(item["path"], str) + item["basepath"] = os.path.relpath(item["basepath"], str(Path(__file__).parent)) + item["path"] = os.path.relpath(item["path"], str(Path(__file__).parent)) + return abs_result + + +@contextmanager +def pushd(path: Path) -> Iterator[None]: + prev = os.getcwd() + os.chdir(path) + try: + yield + finally: + os.chdir(prev) + + class TestExpandModules(CheckerTestCase): """Test the expand_modules function while allowing options to be set.""" @@ -159,6 +193,40 @@ def test_expand_modules( assert modules == expected assert not errors + @pytest.mark.parametrize( + "files_or_modules,expected", + [ + ( + [Path(__file__).name], + {this_file_relative_to_parent["path"]: this_file_relative_to_parent}, + ), + ( + ["./"], + { + module["path"]: module # pylint: disable=unsubscriptable-object + for module in _list_expected_package_modules_relative() + }, + ), + ], + ) + @set_config(ignore_paths="") + def test_expand_modules_relative_path( + self, files_or_modules: list[str], expected: dict[str, ModuleDescriptionDict] + ) -> None: + """Test expand_modules with the default value of ignore-paths and relative path as input.""" + ignore_list: list[str] = [] + ignore_list_re: list[re.Pattern[str]] = [] + with pushd(Path(__file__).parent): + modules, errors = expand_modules( + files_or_modules, + [], + ignore_list, + ignore_list_re, + self.linter.config.ignore_paths, + ) + assert modules == expected + assert not errors + @pytest.mark.parametrize( "files_or_modules,expected", [