summaryrefslogtreecommitdiff
path: root/pylint/testutils/functional/find_functional_tests.py
blob: 0b4dd49e083a9ca6a2d93e9b0cbc642e77932231 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# 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

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 = 49
"""'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'."""

IGNORED_PARENT_DIRS = {
    "deprecated_relative_import",
    "ext",
    "regression",
    "regression_02",
}
"""Direct parent directories that should be ignored."""

IGNORED_PARENT_PARENT_DIRS = {
    "docparams",
    "deprecated_relative_import",
    "ext",
}
"""Parents of direct parent directories that should be ignored."""


def get_functional_test_files_from_directory(
    input_dir: Path | str,
) -> list[FunctionalTestFile]:
    """Get all functional tests in the input_dir."""
    suite = []

    _check_functional_tests_structure(Path(input_dir))

    for dirpath, dirnames, filenames in os.walk(input_dir):
        if dirpath.endswith("__pycache__"):
            continue
        dirnames.sort()
        filenames.sort()
        for filename in filenames:
            if filename != "__init__.py" and filename.endswith(".py"):
                suite.append(FunctionalTestFile(dirpath, filename))
    return suite


def _check_functional_tests_structure(directory: Path) -> 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 walk(path: Path) -> Iterator[Path]:
        violations: list[tuple[Path, int]] = []
        for _file_or_dir in path.iterdir():
            if _file_or_dir.is_dir():
                _files = list(_file_or_dir.iterdir())
                if len(_files) > REASONABLY_DISPLAYABLE_VERTICALLY:
                    violations.append((_file_or_dir, len(_files)))
                yield _file_or_dir
                yield from walk(_file_or_dir)
            else:
                yield _file_or_dir.resolve()
        if violations:
            _msg = "The following directory contains too many functional tests files:\n"
            for offending_file, number in violations:
                _msg += f"- {offending_file}: ({number}) > {REASONABLY_DISPLAYABLE_VERTICALLY}\n"
            raise AssertionError(_msg)

    # Collect all sub-directories and files in directory
    for file_or_dir in walk(directory):
        if file_or_dir.stem.startswith("_"):
            continue
        if file_or_dir.is_dir():
            dirs.add(file_or_dir)
        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]
        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
        ):
            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)