diff options
Diffstat (limited to 'pylint/testutils/functional')
-rw-r--r-- | pylint/testutils/functional/__init__.py | 4 | ||||
-rw-r--r-- | pylint/testutils/functional/find_functional_tests.py | 104 | ||||
-rw-r--r-- | pylint/testutils/functional/lint_module_output_update.py | 4 | ||||
-rw-r--r-- | pylint/testutils/functional/test_file.py | 14 |
4 files changed, 86 insertions, 40 deletions
diff --git a/pylint/testutils/functional/__init__.py b/pylint/testutils/functional/__init__.py index 4840e1ba6..c1c1c5139 100644 --- a/pylint/testutils/functional/__init__.py +++ b/pylint/testutils/functional/__init__.py @@ -1,6 +1,6 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html -# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt +# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt __all__ = [ "FunctionalTestFile", diff --git a/pylint/testutils/functional/find_functional_tests.py b/pylint/testutils/functional/find_functional_tests.py index 200cee7ec..f2e636687 100644 --- a/pylint/testutils/functional/find_functional_tests.py +++ b/pylint/testutils/functional/find_functional_tests.py @@ -1,18 +1,20 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html -# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt +# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt from __future__ import annotations import os +from collections.abc import Iterator from pathlib import Path from pylint.testutils.functional.test_file import FunctionalTestFile -REASONABLY_DISPLAYABLE_VERTICALLY = 48 -"""'Wet finger' number of files that are reasonable to display by an IDE.""" -SHOULD_BE_IN_THE_SAME_DIRECTORY = 5 -"""'Wet finger' as in 'in my settings there are precisely this many'.""" +REASONABLY_DISPLAYABLE_VERTICALLY = 49 +"""'Wet finger' number of files that are reasonable to display by an IDE. + +'Wet finger' as in 'in my settings there are precisely this many'. +""" IGNORED_PARENT_DIRS = { "deprecated_relative_import", @@ -32,11 +34,12 @@ IGNORED_PARENT_PARENT_DIRS = { def get_functional_test_files_from_directory( input_dir: Path | str, + max_file_per_directory: int = REASONABLY_DISPLAYABLE_VERTICALLY, ) -> list[FunctionalTestFile]: """Get all functional tests in the input_dir.""" suite = [] - _check_functional_tests_structure(Path(input_dir)) + _check_functional_tests_structure(Path(input_dir), max_file_per_directory) for dirpath, dirnames, filenames in os.walk(input_dir): if dirpath.endswith("__pycache__"): @@ -49,39 +52,88 @@ def get_functional_test_files_from_directory( return suite -def _check_functional_tests_structure(directory: Path) -> None: - """Check if test directories follow correct file/folder structure.""" - # Ignore underscored directories +def _check_functional_tests_structure( + directory: Path, max_file_per_directory: int +) -> None: + """Check if test directories follow correct file/folder structure. + + Ignore underscored directories or files. + """ if Path(directory).stem.startswith("_"): return files: set[Path] = set() dirs: set[Path] = set() + def _get_files_from_dir( + path: Path, violations: list[tuple[Path, int]] + ) -> list[Path]: + """Return directories and files from a directory and handles violations.""" + files_without_leading_underscore = list( + p for p in path.iterdir() if not p.stem.startswith("_") + ) + if len(files_without_leading_underscore) > max_file_per_directory: + violations.append((path, len(files_without_leading_underscore))) + return files_without_leading_underscore + + def walk(path: Path) -> Iterator[Path]: + violations: list[tuple[Path, int]] = [] + violations_msgs: set[str] = set() + parent_dir_files = _get_files_from_dir(path, violations) + error_msg = ( + "The following directory contains too many functional tests files:\n" + ) + for _file_or_dir in parent_dir_files: + if _file_or_dir.is_dir(): + _files = _get_files_from_dir(_file_or_dir, violations) + yield _file_or_dir.resolve() + try: + yield from walk(_file_or_dir) + except AssertionError as e: + violations_msgs.add(str(e).replace(error_msg, "")) + else: + yield _file_or_dir.resolve() + if violations or violations_msgs: + _msg = error_msg + for offending_file, number in violations: + _msg += f"- {offending_file}: {number} when the max is {max_file_per_directory}\n" + for error_msg in violations_msgs: + _msg += error_msg + raise AssertionError(_msg) + # Collect all sub-directories and files in directory - for file_or_dir in directory.iterdir(): - if file_or_dir.is_file(): - if file_or_dir.suffix == ".py" and not file_or_dir.stem.startswith("_"): - files.add(file_or_dir) - elif file_or_dir.is_dir(): + for file_or_dir in walk(directory): + if file_or_dir.is_dir(): dirs.add(file_or_dir) - _check_functional_tests_structure(file_or_dir) - - assert len(files) <= REASONABLY_DISPLAYABLE_VERTICALLY, ( - f"{directory} contains too many functional tests files " - + f"({len(files)} > {REASONABLY_DISPLAYABLE_VERTICALLY})." - ) + elif file_or_dir.suffix == ".py": + files.add(file_or_dir) + directory_does_not_exists: list[tuple[Path, Path]] = [] + misplaced_file: list[Path] = [] for file in files: possible_dir = file.parent / file.stem.split("_")[0] - assert not possible_dir.exists(), f"{file} should go in {possible_dir}." - + if possible_dir.exists(): + directory_does_not_exists.append((file, possible_dir)) # Exclude some directories as they follow a different structure if ( not len(file.parent.stem) == 1 # First letter sub-directories and file.parent.stem not in IGNORED_PARENT_DIRS and file.parent.parent.stem not in IGNORED_PARENT_PARENT_DIRS ): - assert file.stem.startswith( - file.parent.stem - ), f"{file} should not go in {file.parent}" + if not file.stem.startswith(file.parent.stem): + misplaced_file.append(file) + + if directory_does_not_exists or misplaced_file: + msg = "The following functional tests are disorganized:\n" + for file, possible_dir in directory_does_not_exists: + msg += ( + f"- In '{directory}', '{file.relative_to(directory)}' " + f"should go in '{possible_dir.relative_to(directory)}'\n" + ) + for file in misplaced_file: + msg += ( + f"- In '{directory}', {file.relative_to(directory)} should go in a directory" + f" that starts with the first letters" + f" of '{file.stem}' (not '{file.parent.stem}')\n" + ) + raise AssertionError(msg) diff --git a/pylint/testutils/functional/lint_module_output_update.py b/pylint/testutils/functional/lint_module_output_update.py index e4f88b3ef..8855fd1aa 100644 --- a/pylint/testutils/functional/lint_module_output_update.py +++ b/pylint/testutils/functional/lint_module_output_update.py @@ -1,6 +1,6 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html -# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt +# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt from __future__ import annotations diff --git a/pylint/testutils/functional/test_file.py b/pylint/testutils/functional/test_file.py index 85a72daa9..16593b5c4 100644 --- a/pylint/testutils/functional/test_file.py +++ b/pylint/testutils/functional/test_file.py @@ -1,13 +1,13 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html -# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt +# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt from __future__ import annotations import configparser -import sys from collections.abc import Callable from os.path import basename, exists, join +from typing import TypedDict def parse_python_version(ver_str: str) -> tuple[int, ...]: @@ -19,12 +19,6 @@ class NoFileError(Exception): pass -if sys.version_info >= (3, 8): - from typing import TypedDict -else: - from typing_extensions import TypedDict - - class TestFileOptions(TypedDict): min_pyver: tuple[int, ...] max_pyver: tuple[int, ...] @@ -62,7 +56,7 @@ class FunctionalTestFile: def __init__(self, directory: str, filename: str) -> None: self._directory = directory self.base = filename.replace(".py", "") - # TODO: 2.x: Deprecate FunctionalTestFile.options and related code + # TODO: 3.0: Deprecate FunctionalTestFile.options and related code # We should just parse these options like a normal configuration file. self.options: TestFileOptions = { "min_pyver": (2, 5), |