Skip to content

Commit 8215901

Browse files
Functional tests for pyreverse (#6449)
Co-authored-by: Daniël van Noord <[email protected]>
1 parent 1c2bb5b commit 8215901

File tree

10 files changed

+176
-1
lines changed

10 files changed

+176
-1
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ repos:
2525
hooks:
2626
- id: copyright-notice
2727
args: ["--notice=script/copyright.txt", "--enforce-all"]
28-
exclude: tests/functional/|tests/input|doc/data/messages|examples/|setup.py|tests(/\w*)*data/
28+
exclude: tests(/\w*)*/functional/|tests/input|doc/data/messages|examples/|setup.py|tests(/\w*)*data/
2929
types: [python]
3030
- repo: https://github.com/asottile/pyupgrade
3131
rev: v2.32.0

pylint/testutils/pyreverse.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@
55
from __future__ import annotations
66

77
import argparse
8+
import configparser
9+
import shlex
10+
import sys
11+
from pathlib import Path
12+
from typing import NamedTuple
13+
14+
if sys.version_info >= (3, 8):
15+
from typing import TypedDict
16+
else:
17+
from typing_extensions import TypedDict
818

919

1020
# This class could and should be replaced with a simple dataclass when support for Python < 3.7 is dropped.
@@ -54,3 +64,51 @@ def __init__(
5464
self.ignore_list = ignore_list
5565
self.project = project
5666
self.output_directory = output_directory
67+
68+
69+
class TestFileOptions(TypedDict):
70+
output_formats: list[str]
71+
command_line_args: list[str]
72+
73+
74+
class FunctionalPyreverseTestfile(NamedTuple):
75+
"""Named tuple containing the test file and the expected output."""
76+
77+
source: Path
78+
options: TestFileOptions
79+
80+
81+
def get_functional_test_files(
82+
root_directory: Path,
83+
) -> list[FunctionalPyreverseTestfile]:
84+
"""Get all functional test files from the given directory."""
85+
test_files = []
86+
for path in root_directory.rglob("*.py"):
87+
config_file = path.with_suffix(".rc")
88+
if config_file.exists():
89+
test_files.append(
90+
FunctionalPyreverseTestfile(
91+
source=path, options=_read_config(config_file)
92+
)
93+
)
94+
else:
95+
test_files.append(
96+
FunctionalPyreverseTestfile(
97+
source=path,
98+
options={"output_formats": ["mmd"], "command_line_args": []},
99+
)
100+
)
101+
return test_files
102+
103+
104+
def _read_config(config_file: Path) -> TestFileOptions:
105+
config = configparser.ConfigParser()
106+
config.read(str(config_file))
107+
return {
108+
"output_formats": config.get(
109+
"testoptions", "output_formats", fallback="mmd"
110+
).split(","),
111+
"command_line_args": shlex.split(
112+
config.get("testoptions", "command_line_args", fallback="")
113+
),
114+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
classDiagram
2+
class InstanceAttributes {
3+
my_int_with_type_hint : int
4+
my_int_without_type_hint : int
5+
my_optional_int : Optional[int]
6+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class InstanceAttributes:
2+
def __init__(self):
3+
self.my_int_without_type_hint = 1
4+
self.my_int_with_type_hint: int = 2
5+
self.my_optional_int: int = None
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
@startuml classes
2+
set namespaceSeparator none
3+
class "CheckerCollector" as colorized.CheckerCollector #aliceblue {
4+
checker1
5+
checker2
6+
checker3
7+
}
8+
class "ElseifUsedChecker" as pylint.extensions.check_elif.ElseifUsedChecker #antiquewhite {
9+
msgs : dict
10+
name : str
11+
leave_module(_: nodes.Module) -> None
12+
process_tokens(tokens: list[TokenInfo]) -> None
13+
visit_if(node: nodes.If) -> None
14+
}
15+
class "ExceptionsChecker" as pylint.checkers.exceptions.ExceptionsChecker #aquamarine {
16+
msgs : dict
17+
name : str
18+
options : tuple
19+
open()
20+
visit_binop(node: nodes.BinOp) -> None
21+
visit_compare(node: nodes.Compare) -> None
22+
visit_raise(node: nodes.Raise) -> None
23+
visit_tryexcept(node: nodes.TryExcept) -> None
24+
}
25+
class "StdlibChecker" as pylint.checkers.stdlib.StdlibChecker #aquamarine {
26+
msgs : dict
27+
name : str
28+
deprecated_arguments(method: str)
29+
deprecated_classes(module: str)
30+
deprecated_decorators() -> Iterable
31+
deprecated_methods()
32+
visit_boolop(node: nodes.BoolOp) -> None
33+
visit_call(node: nodes.Call) -> None
34+
visit_functiondef(node: nodes.FunctionDef) -> None
35+
visit_if(node: nodes.If) -> None
36+
visit_ifexp(node: nodes.IfExp) -> None
37+
visit_unaryop(node: nodes.UnaryOp) -> None
38+
}
39+
pylint.checkers.exceptions.ExceptionsChecker --* colorized.CheckerCollector : checker1
40+
pylint.checkers.stdlib.StdlibChecker --* colorized.CheckerCollector : checker3
41+
pylint.extensions.check_elif.ElseifUsedChecker --* colorized.CheckerCollector : checker2
42+
@enduml
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from pylint.checkers.exceptions import ExceptionsChecker
2+
from pylint.checkers.stdlib import StdlibChecker
3+
from pylint.extensions.check_elif import ElseifUsedChecker
4+
5+
6+
class CheckerCollector:
7+
def __init__(self):
8+
self.checker1 = ExceptionsChecker(None)
9+
self.checker2 = ElseifUsedChecker(None)
10+
self.checker3 = StdlibChecker(None)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[testoptions]
2+
output_formats=puml
3+
command_line_args=-S --colorized --max-color-depth=2
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
classDiagram
2+
class Child {
3+
}
4+
class Parent {
5+
}
6+
Child --|> Parent
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class Parent:
2+
"""parent class"""
3+
4+
5+
class Child(Parent):
6+
"""child class"""
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
2+
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
3+
# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt
4+
5+
from pathlib import Path
6+
7+
import pytest
8+
from py._path.local import LocalPath # type: ignore[import]
9+
10+
from pylint.pyreverse.main import Run
11+
from pylint.testutils.pyreverse import (
12+
FunctionalPyreverseTestfile,
13+
get_functional_test_files,
14+
)
15+
16+
FUNCTIONAL_DIR = Path(__file__).parent / "functional"
17+
CLASS_DIAGRAM_TESTS = get_functional_test_files(FUNCTIONAL_DIR / "class_diagrams")
18+
CLASS_DIAGRAM_TEST_IDS = [testfile.source.stem for testfile in CLASS_DIAGRAM_TESTS]
19+
20+
21+
@pytest.mark.parametrize(
22+
"testfile",
23+
CLASS_DIAGRAM_TESTS,
24+
ids=CLASS_DIAGRAM_TEST_IDS,
25+
)
26+
def test_class_diagrams(
27+
testfile: FunctionalPyreverseTestfile, tmpdir: LocalPath
28+
) -> None:
29+
input_file = testfile.source
30+
for output_format in testfile.options["output_formats"]:
31+
with pytest.raises(SystemExit) as sys_exit:
32+
args = ["-o", f"{output_format}", "-d", str(tmpdir)]
33+
args.extend(testfile.options["command_line_args"])
34+
args += [str(input_file)]
35+
Run(args)
36+
assert sys_exit.value.code == 0
37+
assert testfile.source.with_suffix(f".{output_format}").read_text(
38+
encoding="utf8"
39+
) == Path(tmpdir / f"classes.{output_format}").read_text(encoding="utf8")

0 commit comments

Comments
 (0)