From a81840ebfa367632ebc8eeaad8c86cb886a5fc20 Mon Sep 17 00:00:00 2001 From: Julfried Date: Mon, 13 Apr 2026 00:02:41 +0200 Subject: [PATCH 01/12] First shot at a pyreverse primer implementation based on the existing primer --- pylint/testutils/_primer/pyreverse_primer.py | 137 +++++++++++++++++ .../_primer/pyreverse_primer_command.py | 41 ++++++ .../pyreverse_primer_compare_command.py | 139 ++++++++++++++++++ .../_primer/pyreverse_primer_run_command.py | 59 ++++++++ .../_primer/pyreverse_primer_target.py | 39 +++++ 5 files changed, 415 insertions(+) create mode 100644 pylint/testutils/_primer/pyreverse_primer.py create mode 100644 pylint/testutils/_primer/pyreverse_primer_command.py create mode 100644 pylint/testutils/_primer/pyreverse_primer_compare_command.py create mode 100644 pylint/testutils/_primer/pyreverse_primer_run_command.py create mode 100644 pylint/testutils/_primer/pyreverse_primer_target.py diff --git a/pylint/testutils/_primer/pyreverse_primer.py b/pylint/testutils/_primer/pyreverse_primer.py new file mode 100644 index 0000000000..809d5ace89 --- /dev/null +++ b/pylint/testutils/_primer/pyreverse_primer.py @@ -0,0 +1,137 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# 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 argparse +import json +import sys +from pathlib import Path + +from pylint.testutils._primer import PackageToLint +from pylint.testutils._primer.primer_prepare_command import PrepareCommand +from pylint.testutils._primer.pyreverse_primer_command import PyreversePrimerCommand +from pylint.testutils._primer.pyreverse_primer_compare_command import CompareCommand +from pylint.testutils._primer.pyreverse_primer_run_command import RunCommand +from pylint.testutils._primer.pyreverse_primer_target import PyreversePrimerTarget + + +class PyreversePrimer: + """Main class to handle pyreverse primer snapshots.""" + + def __init__( + self, + primer_directory: Path, + packages_path: Path, + targets_path: Path, + ) -> None: + self.primer_directory = primer_directory + self._argument_parser = argparse.ArgumentParser(prog="Pylint Pyreverse Primer") + self._subparsers = self._argument_parser.add_subparsers( + dest="command", required=True + ) + + prepare_parser = self._subparsers.add_parser("prepare") + prepare_parser.add_argument( + "--clone", help="Clone all packages.", action="store_true", default=False + ) + prepare_parser.add_argument( + "--check", + help="Check consistencies and commits of all packages.", + action="store_true", + default=False, + ) + prepare_parser.add_argument( + "--make-commit-string", + help="Get latest commit string.", + action="store_true", + default=False, + ) + prepare_parser.add_argument( + "--read-commit-string", + help="Print latest commit string.", + action="store_true", + default=False, + ) + + run_parser = self._subparsers.add_parser("run") + run_parser.add_argument( + "--type", choices=["main", "pr"], required=True, help="Type of primer run." + ) + + compare_parser = self._subparsers.add_parser("compare") + compare_parser.add_argument( + "--base-file", + required=True, + help="Location of output file of the base run.", + ) + compare_parser.add_argument( + "--new-file", + required=True, + help="Location of output file of the new run.", + ) + compare_parser.add_argument( + "--commit", + required=True, + help="Commit hash of the PR commit being checked.", + ) + + self.config = self._argument_parser.parse_args() + self.targets = self._get_targets_from_json(targets_path) + self.packages = self._get_packages_to_prime_from_json( + packages_path, self.targets + ) + + if self.config.command == "prepare": + self.command: PrepareCommand | PyreversePrimerCommand = PrepareCommand( + self.primer_directory, self.packages, self.config + ) + elif self.config.command == "run": + self.command = RunCommand( + self.primer_directory, self.packages, self.targets, self.config + ) + else: + self.command = CompareCommand( + self.primer_directory, self.packages, self.targets, self.config + ) + + def run(self) -> None: + self.command.run() + + @staticmethod + def _minimum_python_supported(package_data: dict[str, str]) -> bool: + min_python_str = package_data.get("minimum_python", None) + if not min_python_str: + return True + min_python_tuple = tuple(int(n) for n in min_python_str.split(".")) + return min_python_tuple <= sys.version_info[:2] + + @staticmethod + def _get_targets_from_json( + json_path: Path, + ) -> dict[str, PyreversePrimerTarget]: + with open(json_path, encoding="utf-8") as stream: + return { + name: PyreversePrimerTarget(**target_data) + for name, target_data in json.load(stream).items() + } + + @staticmethod + def _get_packages_to_prime_from_json( + json_path: Path, + targets: dict[str, PyreversePrimerTarget], + ) -> dict[str, PackageToLint]: + with open(json_path, encoding="utf-8") as stream: + packages_to_prime = { + name: PackageToLint(**package_data) + for name, package_data in json.load(stream).items() + if PyreversePrimer._minimum_python_supported(package_data) + } + + package_names = {target.package for target in targets.values()} + return { + package_name: packages_to_prime[package_name] + for package_name in package_names + if package_name in packages_to_prime + } diff --git a/pylint/testutils/_primer/pyreverse_primer_command.py b/pylint/testutils/_primer/pyreverse_primer_command.py new file mode 100644 index 0000000000..d1f2fb4b3b --- /dev/null +++ b/pylint/testutils/_primer/pyreverse_primer_command.py @@ -0,0 +1,41 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# 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 abc +import argparse +from pathlib import Path +from typing import TypedDict + +from pylint.testutils._primer import PackageToLint +from pylint.testutils._primer.pyreverse_primer_target import PyreversePrimerTarget + + +class PyreverseTargetData(TypedDict): + commit: str + diagram: str + + +PyreversePrimerOutput = dict[str, PyreverseTargetData] + + +class PyreversePrimerCommand: + """Base command for the pyreverse primer.""" + + def __init__( + self, + primer_directory: Path, + packages: dict[str, PackageToLint], + targets: dict[str, PyreversePrimerTarget], + config: argparse.Namespace, + ) -> None: + self.primer_directory = primer_directory + self.packages = packages + self.targets = targets + self.config = config + + @abc.abstractmethod + def run(self) -> None: + pass diff --git a/pylint/testutils/_primer/pyreverse_primer_compare_command.py b/pylint/testutils/_primer/pyreverse_primer_compare_command.py new file mode 100644 index 0000000000..29c9c4cddc --- /dev/null +++ b/pylint/testutils/_primer/pyreverse_primer_compare_command.py @@ -0,0 +1,139 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# 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 json +from collections.abc import Iterator +from difflib import unified_diff +from pathlib import Path + +from pylint.testutils._primer.pyreverse_primer_command import ( + PyreversePrimerCommand, + PyreversePrimerOutput, + PyreverseTargetData, +) + +MAX_GITHUB_COMMENT_LENGTH = 65536 +COMMENT_MARKER = "\n" + + +class CompareCommand(PyreversePrimerCommand): + """Compare pyreverse primer outputs and render a GitHub comment.""" + + def run(self) -> None: + base_data = self._load_json(self.config.base_file) + new_data = self._load_json(self.config.new_file) + comment = self._create_comment(base_data, new_data) + with open( + self.primer_directory / "comment.txt", "w", encoding="utf-8" + ) as stream: + stream.write(comment) + + def _create_comment( + self, base_data: PyreversePrimerOutput, new_data: PyreversePrimerOutput + ) -> str: + comment = "" + for target_name, base_target_data, new_target_data in self._iter_changes( + base_data, new_data + ): + if len(comment) >= MAX_GITHUB_COMMENT_LENGTH: + break + comment += self._create_comment_for_target( + target_name, base_target_data, new_target_data + ) + + comment = ( + COMMENT_MARKER + + f"🤖 **Effect of this PR on tracked pyreverse diagrams:** 🤖\n\n{comment}" + if comment + else ( + COMMENT_MARKER + + "🤖 According to the pyreverse primer, this change has **no effect** on" + " the tracked diagrams. 🤖🎉\n\n" + ) + ) + return self._truncate_comment(comment) + + def _create_comment_for_target( + self, + target_name: str, + base_target_data: PyreverseTargetData, + new_target_data: PyreverseTargetData, + ) -> str: + target = self.targets[target_name] + package = self.packages[target.package] + diff = self._diagram_diff( + base_target_data["diagram"], + new_target_data["diagram"], + ) + return ( + f"**Effect on `{target.display_name}` in [{target.package}]({package.url}):**\n\n" + "
\nDiagram diff\n\n" + f"```diff\n{diff}```\n" + "
\n\n" + "
\nRendered diagram\n\n" + f"```mermaid\n{new_target_data['diagram'].rstrip()}\n```\n" + "
\n\n" + ) + + def _truncate_comment(self, comment: str) -> str: + hash_information = ( + f"*This comment was generated for commit {self.config.commit}*" + ) + if len(comment) + len(hash_information) >= MAX_GITHUB_COMMENT_LENGTH: + truncation_information = ( + "*This comment was truncated because GitHub allows only" + f" {MAX_GITHUB_COMMENT_LENGTH} characters in a comment.*" + ) + suffix = f"\n{truncation_information}\n\n" + closing_tag = "\n" + max_len = ( + MAX_GITHUB_COMMENT_LENGTH + - len(hash_information) + - len(suffix) + - len(closing_tag) + ) + cut_point = comment.rfind(" ", 0, max_len - 10) + if cut_point > 0: + comment = comment[:cut_point] + "...\n" + else: + comment = comment[: max_len - 10] + "...\n" + if comment.count("
") > comment.count("
"): + comment += closing_tag + comment += suffix + comment += hash_information + return comment + + @staticmethod + def _diagram_diff(base_diagram: str, new_diagram: str) -> str: + return ( + "\n".join( + unified_diff( + base_diagram.splitlines(), + new_diagram.splitlines(), + fromfile="main", + tofile="pr", + lineterm="", + ) + ) + + "\n" + ) + + @staticmethod + def _iter_changes( + base_data: PyreversePrimerOutput, + new_data: PyreversePrimerOutput, + ) -> Iterator[tuple[str, PyreverseTargetData, PyreverseTargetData]]: + for target_name, base_target_data in base_data.items(): + new_target_data = new_data[target_name] + if base_target_data["diagram"] == new_target_data["diagram"]: + continue + yield target_name, base_target_data, new_target_data + + @staticmethod + def _load_json(file_path: Path | str) -> PyreversePrimerOutput: + with open(file_path, encoding="utf-8") as stream: + result: PyreversePrimerOutput = json.load(stream) + return result diff --git a/pylint/testutils/_primer/pyreverse_primer_run_command.py b/pylint/testutils/_primer/pyreverse_primer_run_command.py new file mode 100644 index 0000000000..eff7313f80 --- /dev/null +++ b/pylint/testutils/_primer/pyreverse_primer_run_command.py @@ -0,0 +1,59 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# 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 json +import os +import sys +import tempfile +from pathlib import Path + +from git.repo import Repo + +from pylint.pyreverse.main import Run +from pylint.testutils._primer.pyreverse_primer_command import ( + PyreversePrimerCommand, + PyreversePrimerOutput, + PyreverseTargetData, +) + + +class RunCommand(PyreversePrimerCommand): + """Generate pyreverse diagrams for all configured primer targets.""" + + def run(self) -> None: + output: PyreversePrimerOutput = {} + for target_name, target in self.targets.items(): + package = self.packages[target.package] + local_commit = Repo(package.clone_directory).head.object.hexsha + output[target_name] = PyreverseTargetData( + commit=local_commit, + diagram=self._render_target(package.clone_directory, target_name), + ) + + path = self.primer_directory / ( + f"pyreverse_output_{'.'.join(str(i) for i in sys.version_info[:2])}_{self.config.type}.txt" + ) + print(f"Writing result in {path}") + with open(path, "w", encoding="utf-8") as stream: + json.dump(output, stream) + + def _render_target(self, package_directory: Path, target_name: str) -> str: + target = self.targets[target_name] + current_directory = Path.cwd() + with tempfile.TemporaryDirectory() as tmpdir: + try: + os.chdir(package_directory) + exit_code = Run(target.pyreverse_args(tmpdir)).run() + finally: + os.chdir(current_directory) + if exit_code != 0: + raise RuntimeError( + f"Pyreverse failed for target '{target_name}' with exit code {exit_code}." + ) + + diagram_path = Path(tmpdir) / f"{target.output_name}.mmd" + with open(diagram_path, encoding="utf-8") as stream: + return stream.read() diff --git a/pylint/testutils/_primer/pyreverse_primer_target.py b/pylint/testutils/_primer/pyreverse_primer_target.py new file mode 100644 index 0000000000..aad11c1160 --- /dev/null +++ b/pylint/testutils/_primer/pyreverse_primer_target.py @@ -0,0 +1,39 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# 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 + +from dataclasses import dataclass, field + + +@dataclass(frozen=True) +class PyreversePrimerTarget: + """Single pyreverse diagram target to snapshot and compare.""" + + package: str + class_name: str + path: str + pyreverse_additional_args: list[str] = field(default_factory=list) + + @property + def display_name(self) -> str: + return self.class_name.rsplit(".", maxsplit=1)[-1] + + @property + def output_name(self) -> str: + return self.class_name + + def pyreverse_args(self, output_directory: str) -> list[str]: + return [ + "-o", + "mmd", + "-d", + output_directory, + "-c", + self.class_name, + "--only-classnames", + "--no-standalone", + *self.pyreverse_additional_args, + self.path, + ] From 4192b4636ddf80f342a0fb38c8b82ec84d98d086 Mon Sep 17 00:00:00 2001 From: Julfried Date: Mon, 13 Apr 2026 00:10:06 +0200 Subject: [PATCH 02/12] Exclude primer testcase from beeing reformatted by prettier --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eba80c5e6c..5596b1a36f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -143,7 +143,7 @@ repos: hooks: - id: prettier args: [--prose-wrap=always, --print-width=88] - exclude: (tests(/\w*)*data/|.github/FUNDING.yml) + exclude: (tests(/\w*)*data/|tests/testutils/_primer/pyreverse_cases/|.github/FUNDING.yml) - repo: https://github.com/DanielNoord/pydocstringformatter rev: v0.7.5 hooks: From 04c84fd7f0071074a2d4a9537780afb6fc75fbba Mon Sep 17 00:00:00 2001 From: Julfried Date: Mon, 13 Apr 2026 00:10:35 +0200 Subject: [PATCH 03/12] Add unit tests for the expected output of the primer --- .../pyreverse_cases/changed/expected.md | 31 ++++ .../_primer/pyreverse_cases/changed/main.json | 6 + .../_primer/pyreverse_cases/changed/pr.json | 6 + .../multiple_targets/expected.md | 58 +++++++ .../multiple_targets/expected_truncated.md | 26 ++++ .../multiple_targets/main.json | 10 ++ .../pyreverse_cases/multiple_targets/pr.json | 10 ++ .../pyreverse_cases/no_change/expected.md | 4 + .../pyreverse_cases/no_change/main.json | 6 + .../_primer/pyreverse_cases/no_change/pr.json | 6 + .../_primer/test_pyreverse_primer.py | 145 ++++++++++++++++++ 11 files changed, 308 insertions(+) create mode 100644 tests/testutils/_primer/pyreverse_cases/changed/expected.md create mode 100644 tests/testutils/_primer/pyreverse_cases/changed/main.json create mode 100644 tests/testutils/_primer/pyreverse_cases/changed/pr.json create mode 100644 tests/testutils/_primer/pyreverse_cases/multiple_targets/expected.md create mode 100644 tests/testutils/_primer/pyreverse_cases/multiple_targets/expected_truncated.md create mode 100644 tests/testutils/_primer/pyreverse_cases/multiple_targets/main.json create mode 100644 tests/testutils/_primer/pyreverse_cases/multiple_targets/pr.json create mode 100644 tests/testutils/_primer/pyreverse_cases/no_change/expected.md create mode 100644 tests/testutils/_primer/pyreverse_cases/no_change/main.json create mode 100644 tests/testutils/_primer/pyreverse_cases/no_change/pr.json create mode 100644 tests/testutils/_primer/test_pyreverse_primer.py diff --git a/tests/testutils/_primer/pyreverse_cases/changed/expected.md b/tests/testutils/_primer/pyreverse_cases/changed/expected.md new file mode 100644 index 0000000000..f24df96dff --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/changed/expected.md @@ -0,0 +1,31 @@ + +🤖 **Effect of this PR on tracked pyreverse diagrams:** 🤖 + +**Effect on `ClassDef` in [astroid](https://github.com/pylint-dev/astroid):** + +
+Diagram diff + +```diff +--- main ++++ pr +@@ -1,3 +1,4 @@ + classDiagram + class ClassDef { ++ infer() + } +``` +
+ +
+Rendered diagram + +```mermaid +classDiagram + class ClassDef { + infer() + } +``` +
+ +*This comment was generated for commit deadbeef* diff --git a/tests/testutils/_primer/pyreverse_cases/changed/main.json b/tests/testutils/_primer/pyreverse_cases/changed/main.json new file mode 100644 index 0000000000..5b31907abd --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/changed/main.json @@ -0,0 +1,6 @@ +{ + "classdef": { + "commit": "1234567890abcdef", + "diagram": "classDiagram\n class ClassDef {\n }\n" + } +} diff --git a/tests/testutils/_primer/pyreverse_cases/changed/pr.json b/tests/testutils/_primer/pyreverse_cases/changed/pr.json new file mode 100644 index 0000000000..46f980e761 --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/changed/pr.json @@ -0,0 +1,6 @@ +{ + "classdef": { + "commit": "1234567890abcdef", + "diagram": "classDiagram\n class ClassDef {\n infer()\n }\n" + } +} diff --git a/tests/testutils/_primer/pyreverse_cases/multiple_targets/expected.md b/tests/testutils/_primer/pyreverse_cases/multiple_targets/expected.md new file mode 100644 index 0000000000..8c0c231cd2 --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/multiple_targets/expected.md @@ -0,0 +1,58 @@ + +🤖 **Effect of this PR on tracked pyreverse diagrams:** 🤖 + +**Effect on `ClassDef` in [astroid](https://github.com/pylint-dev/astroid):** + +
+Diagram diff + +```diff +--- main ++++ pr +@@ -1,3 +1,4 @@ + classDiagram + class ClassDef { ++ infer() + } +``` +
+ +
+Rendered diagram + +```mermaid +classDiagram + class ClassDef { + infer() + } +``` +
+ +**Effect on `FunctionDef` in [astroid](https://github.com/pylint-dev/astroid):** + +
+Diagram diff + +```diff +--- main ++++ pr +@@ -1,3 +1,4 @@ + classDiagram + class FunctionDef { ++ args + } +``` +
+ +
+Rendered diagram + +```mermaid +classDiagram + class FunctionDef { + args + } +``` +
+ +*This comment was generated for commit deadbeef* diff --git a/tests/testutils/_primer/pyreverse_cases/multiple_targets/expected_truncated.md b/tests/testutils/_primer/pyreverse_cases/multiple_targets/expected_truncated.md new file mode 100644 index 0000000000..3fa456e864 --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/multiple_targets/expected_truncated.md @@ -0,0 +1,26 @@ + +🤖 **Effect of this PR on tracked pyreverse diagrams:** 🤖 + +**Effect on `ClassDef` in [astroid](https://github.com/pylint-dev/astroid):** + +
+Diagram diff + +```diff +--- main ++++ pr +@@ -1,3 +1,4 @@ + classDiagram + class ClassDef { ++ infer() + } +``` +
+ +
+Rendered... +
+ +*This comment was truncated because GitHub allows only 525 characters in a comment.* + +*This comment was generated for commit deadbeef* diff --git a/tests/testutils/_primer/pyreverse_cases/multiple_targets/main.json b/tests/testutils/_primer/pyreverse_cases/multiple_targets/main.json new file mode 100644 index 0000000000..0563f4bf0d --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/multiple_targets/main.json @@ -0,0 +1,10 @@ +{ + "classdef": { + "commit": "1234567890abcdef", + "diagram": "classDiagram\n class ClassDef {\n }\n" + }, + "functiondef": { + "commit": "1234567890abcdef", + "diagram": "classDiagram\n class FunctionDef {\n }\n" + } +} diff --git a/tests/testutils/_primer/pyreverse_cases/multiple_targets/pr.json b/tests/testutils/_primer/pyreverse_cases/multiple_targets/pr.json new file mode 100644 index 0000000000..acfb2363f6 --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/multiple_targets/pr.json @@ -0,0 +1,10 @@ +{ + "classdef": { + "commit": "1234567890abcdef", + "diagram": "classDiagram\n class ClassDef {\n infer()\n }\n" + }, + "functiondef": { + "commit": "1234567890abcdef", + "diagram": "classDiagram\n class FunctionDef {\n args\n }\n" + } +} diff --git a/tests/testutils/_primer/pyreverse_cases/no_change/expected.md b/tests/testutils/_primer/pyreverse_cases/no_change/expected.md new file mode 100644 index 0000000000..d48973ca7e --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/no_change/expected.md @@ -0,0 +1,4 @@ + +🤖 According to the pyreverse primer, this change has **no effect** on the tracked diagrams. 🤖🎉 + +*This comment was generated for commit deadbeef* diff --git a/tests/testutils/_primer/pyreverse_cases/no_change/main.json b/tests/testutils/_primer/pyreverse_cases/no_change/main.json new file mode 100644 index 0000000000..5b31907abd --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/no_change/main.json @@ -0,0 +1,6 @@ +{ + "classdef": { + "commit": "1234567890abcdef", + "diagram": "classDiagram\n class ClassDef {\n }\n" + } +} diff --git a/tests/testutils/_primer/pyreverse_cases/no_change/pr.json b/tests/testutils/_primer/pyreverse_cases/no_change/pr.json new file mode 100644 index 0000000000..5b31907abd --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/no_change/pr.json @@ -0,0 +1,6 @@ +{ + "classdef": { + "commit": "1234567890abcdef", + "diagram": "classDiagram\n class ClassDef {\n }\n" + } +} diff --git a/tests/testutils/_primer/test_pyreverse_primer.py b/tests/testutils/_primer/test_pyreverse_primer.py new file mode 100644 index 0000000000..99cb1ab9b7 --- /dev/null +++ b/tests/testutils/_primer/test_pyreverse_primer.py @@ -0,0 +1,145 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt + +"""Test the pyreverse primer commands.""" + +from __future__ import annotations + +import json +import sys +from pathlib import Path +from unittest.mock import Mock, patch + +import pytest +from _pytest.capture import CaptureFixture + +from pylint.testutils._primer.pyreverse_primer import PyreversePrimer + +HERE = Path(__file__).parent +TEST_DIR_ROOT = HERE.parent.parent +PACKAGES_TO_PRIME_PATH = TEST_DIR_ROOT / "primer/packages_to_prime.json" +PYREVERSE_TARGETS_TO_PRIME_PATH = ( + TEST_DIR_ROOT / "primer/pyreverse_targets_to_prime.json" +) +CASES_PATH = HERE / "pyreverse_cases" + +DEFAULT_ARGS = [ + "python tests/primer/pyreverse_primer.py", + "compare", + "--commit=deadbeef", +] + + +@pytest.mark.parametrize("args", [[], ["wrong_command"]]) +def test_pyreverse_primer_launch_bad_args( + args: list[str], capsys: CaptureFixture[str], tmp_path: Path +) -> None: + with pytest.raises(SystemExit): + with patch("sys.argv", ["python tests/primer/pyreverse_primer.py", *args]): + PyreversePrimer( + tmp_path, + PACKAGES_TO_PRIME_PATH, + PYREVERSE_TARGETS_TO_PRIME_PATH, + ).run() + out, err = capsys.readouterr() + assert not out + assert "usage: Pylint Pyreverse Primer" in err + + +@pytest.mark.parametrize( + "directory", + [ + pytest.param(path, id=path.name) + for path in CASES_PATH.iterdir() + if path.is_dir() + ], +) +def test_compare(directory: Path, tmp_path: Path) -> None: + expected_file = directory / "expected.md" + with patch( + "sys.argv", + [ + *DEFAULT_ARGS, + f"--base-file={directory / 'main.json'}", + f"--new-file={directory / 'pr.json'}", + ], + ): + PyreversePrimer( + tmp_path, + PACKAGES_TO_PRIME_PATH, + PYREVERSE_TARGETS_TO_PRIME_PATH, + ).run() + content = (tmp_path / "comment.txt").read_text(encoding="utf-8") + expected = expected_file.read_text(encoding="utf-8") + assert content == expected.rstrip("\n") + + +def test_truncated_compare(tmp_path: Path) -> None: + directory = CASES_PATH / "multiple_targets" + with patch( + "pylint.testutils._primer.pyreverse_primer_compare_command.MAX_GITHUB_COMMENT_LENGTH", + 525, + ): + with patch( + "sys.argv", + [ + *DEFAULT_ARGS, + f"--base-file={directory / 'main.json'}", + f"--new-file={directory / 'pr.json'}", + ], + ): + PyreversePrimer( + tmp_path, + PACKAGES_TO_PRIME_PATH, + PYREVERSE_TARGETS_TO_PRIME_PATH, + ).run() + content = (tmp_path / "comment.txt").read_text(encoding="utf-8") + expected = (directory / "expected_truncated.md").read_text(encoding="utf-8") + assert content == expected.rstrip("\n") + assert len(content) < 525 + + +def test_run_writes_output(tmp_path: Path) -> None: + fake_repo = Mock() + fake_repo.head.object.hexsha = "1234567890abcdef" + + def _render_diagram(*_: object, target_name: str) -> str: + return f"classDiagram\n class {target_name} {{\n }}\n" + + with patch( + "pylint.testutils._primer.pyreverse_primer_run_command.Repo", + return_value=fake_repo, + ): + with patch( + "pylint.testutils._primer.pyreverse_primer_run_command.RunCommand._render_target", + autospec=True, + side_effect=lambda _self, _package_directory, target_name: _render_diagram( + target_name=target_name + ), + ) as mock_render_target: + with patch( + "sys.argv", + [ + "python tests/primer/pyreverse_primer.py", + "run", + "--type=main", + ], + ): + PyreversePrimer( + tmp_path, + PACKAGES_TO_PRIME_PATH, + PYREVERSE_TARGETS_TO_PRIME_PATH, + ).run() + + output_path = ( + tmp_path + / f"pyreverse_output_{'.'.join(str(i) for i in sys.version_info[:2])}_main.txt" + ) + content = json.loads(output_path.read_text(encoding="utf-8")) + assert mock_render_target.call_count == 3 + assert list(content) == ["classdef", "functiondef", "assignname"] + assert content["classdef"] == { + "commit": "1234567890abcdef", + "diagram": "classDiagram\n class classdef {\n }\n", + } From 1fbf44ef982429952ad4e42e6d7bfa03e0ac64c1 Mon Sep 17 00:00:00 2001 From: Julfried Date: Mon, 13 Apr 2026 00:17:40 +0200 Subject: [PATCH 04/12] Add primer + config --- tests/primer/pyreverse_primer.py | 24 ++++++++++++++++++++ tests/primer/pyreverse_targets_to_prime.json | 17 ++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 tests/primer/pyreverse_primer.py create mode 100644 tests/primer/pyreverse_targets_to_prime.json diff --git a/tests/primer/pyreverse_primer.py b/tests/primer/pyreverse_primer.py new file mode 100644 index 0000000000..dc54d83fa0 --- /dev/null +++ b/tests/primer/pyreverse_primer.py @@ -0,0 +1,24 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# 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 + +from pathlib import Path + +from pylint.testutils._primer.pyreverse_primer import PyreversePrimer + +PRIMER_DIRECTORY = Path(__file__).parent.parent / ".pyreverse_primer_tests/" +PACKAGES_TO_PRIME_PATH = Path(__file__).parent / "packages_to_prime.json" +PYREVERSE_TARGETS_TO_PRIME_PATH = ( + Path(__file__).parent / "pyreverse_targets_to_prime.json" +) + + +if __name__ == "__main__": + primer = PyreversePrimer( + PRIMER_DIRECTORY, + PACKAGES_TO_PRIME_PATH, + PYREVERSE_TARGETS_TO_PRIME_PATH, + ) + primer.run() diff --git a/tests/primer/pyreverse_targets_to_prime.json b/tests/primer/pyreverse_targets_to_prime.json new file mode 100644 index 0000000000..edf627f550 --- /dev/null +++ b/tests/primer/pyreverse_targets_to_prime.json @@ -0,0 +1,17 @@ +{ + "classdef": { + "package": "astroid", + "class_name": "astroid.nodes.scoped_nodes.scoped_nodes.ClassDef", + "path": "astroid" + }, + "functiondef": { + "package": "astroid", + "class_name": "astroid.nodes.scoped_nodes.scoped_nodes.FunctionDef", + "path": "astroid" + }, + "assignname": { + "package": "astroid", + "class_name": "astroid.nodes.node_classes.AssignName", + "path": "astroid" + } +} From 48eb6595d52b592a5394ce49d219de4b6877ab32 Mon Sep 17 00:00:00 2001 From: Julfried Date: Mon, 13 Apr 2026 00:18:36 +0200 Subject: [PATCH 05/12] Take over gh actions from pylint primer for now --- .../workflows/pyreverse_primer_comment.yaml | 132 +++++++++++++ .../workflows/pyreverse_primer_run_main.yaml | 116 ++++++++++++ .../workflows/pyreverse_primer_run_pr.yaml | 175 ++++++++++++++++++ 3 files changed, 423 insertions(+) create mode 100644 .github/workflows/pyreverse_primer_comment.yaml create mode 100644 .github/workflows/pyreverse_primer_run_main.yaml create mode 100644 .github/workflows/pyreverse_primer_run_pr.yaml diff --git a/.github/workflows/pyreverse_primer_comment.yaml b/.github/workflows/pyreverse_primer_comment.yaml new file mode 100644 index 0000000000..acf0485d4a --- /dev/null +++ b/.github/workflows/pyreverse_primer_comment.yaml @@ -0,0 +1,132 @@ +name: Pyreverse Primer / Comment + +on: + workflow_run: + workflows: [Pyreverse Primer / Run] + types: + - completed + +env: + CACHE_VERSION: 1 + KEY_PREFIX: venv-pyreverse-primer + DEFAULT_PYTHON: "3.13" + COMMENT_MARKER: "" + +permissions: + contents: read + pull-requests: write + +jobs: + primer-comment: + if: ${{ github.event.workflow_run.conclusion == 'success' }} + name: Run + runs-on: ubuntu-latest + steps: + - name: Check out code from GitHub + uses: actions/checkout@v6.0.2 + - name: Set up Python ${{ env.DEFAULT_PYTHON }} + id: python + uses: actions/setup-python@v6.2.0 + with: + python-version: ${{ env.DEFAULT_PYTHON }} + check-latest: true + - name: Restore Python virtual environment + id: cache-venv + uses: actions/cache@v5.0.4 + with: + path: venv + key: >- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + env.KEY_PREFIX }}-${{ env.CACHE_VERSION }}-${{ hashFiles('pyproject.toml', + 'requirements_test.txt', 'requirements_test_min.txt', + 'requirements_test_pre_commit.txt') }} + - name: Download outputs + id: download-outputs + uses: actions/github-script@v8.0.0 + with: + script: | + const fs = require('fs'); + const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: ${{ github.event.workflow_run.id }}, + }); + const wantedNames = new Set([ + 'pyreverse_primer_output_main', + 'pyreverse_primer_output_pr', + 'pr_number', + ]); + const wanted = artifacts.data.artifacts.filter((artifact) => wantedNames.has(artifact.name)); + core.setOutput('found', wanted.length === wantedNames.size ? 'true' : 'false'); + for (const artifact of wanted) { + const downloaded = await github.rest.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: artifact.id, + archive_format: 'zip', + }); + fs.writeFileSync(`${artifact.name}.zip`, Buffer.from(downloaded.data)); + } + - name: Unzip outputs + if: steps.download-outputs.outputs.found == 'true' + run: | + unzip pyreverse_primer_output_main.zip + unzip pyreverse_primer_output_pr.zip + unzip pr_number.zip + - name: Check label state + id: check-label + if: steps.download-outputs.outputs.found == 'true' + uses: actions/github-script@v8.0.0 + with: + script: | + const fs = require('fs'); + const prNumber = parseInt(fs.readFileSync('pr_number.txt', { encoding: 'utf8' })); + const pr = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber, + }); + const hasLabel = pr.data.labels.some((label) => label.name === 'pyreverse'); + core.setOutput('has-label', hasLabel ? 'true' : 'false'); + core.setOutput('pr-number', `${prNumber}`); + - name: Compare outputs + if: steps.check-label.outputs.has-label == 'true' + run: | + . venv/bin/activate + python tests/primer/pyreverse_primer.py compare \ + --commit=${{ github.event.workflow_run.head_sha }} \ + --base-file=pyreverse_output_${{ env.DEFAULT_PYTHON }}_main.txt \ + --new-file=pyreverse_output_${{ env.DEFAULT_PYTHON }}_pr.txt + - name: Create or update comment + if: steps.check-label.outputs.has-label == 'true' + uses: actions/github-script@v8.0.0 + with: + script: | + const fs = require('fs'); + const comment = fs.readFileSync('tests/.pyreverse_primer_tests/comment.txt', { encoding: 'utf8' }); + const prNumber = parseInt(fs.readFileSync('pr_number.txt', { encoding: 'utf8' })); + const comments = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + per_page: 100, + }); + const existing = comments.data.find((candidate) => + candidate.body && candidate.body.includes(process.env.COMMENT_MARKER) + ); + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body: comment, + }); + return existing.id; + } + const created = await github.rest.issues.createComment({ + issue_number: prNumber, + owner: context.repo.owner, + repo: context.repo.repo, + body: comment, + }); + return created.data.id; diff --git a/.github/workflows/pyreverse_primer_run_main.yaml b/.github/workflows/pyreverse_primer_run_main.yaml new file mode 100644 index 0000000000..8be1560f2b --- /dev/null +++ b/.github/workflows/pyreverse_primer_run_main.yaml @@ -0,0 +1,116 @@ +name: Pyreverse Primer / Main + +on: + push: + branches: + - main + paths: + - "pylint/pyreverse/**" + - "pylint/testutils/_primer/**" + - "tests/primer/packages_to_prime.json" + - "tests/primer/pyreverse_primer.py" + - "tests/primer/pyreverse_targets_to_prime.json" + - "tests/primer/test_primer_stdlib.py" + - "tests/pyreverse/**" + - "requirements*" + - ".github/workflows/pyreverse_primer*.yaml" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + CACHE_VERSION: 1 + KEY_PREFIX: venv-pyreverse-primer + DEFAULT_PYTHON: "3.13" + +permissions: + contents: read + +jobs: + run-primer: + name: Run + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Check out code from GitHub + uses: actions/checkout@v6.0.2 + - name: Set up Python ${{ env.DEFAULT_PYTHON }} + id: python + uses: actions/setup-python@v6.2.0 + with: + python-version: ${{ env.DEFAULT_PYTHON }} + check-latest: true + - name: Restore Python virtual environment cache + id: cache-venv + uses: actions/cache/restore@v5.0.4 + with: + path: venv + key: >- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + env.KEY_PREFIX }}-${{ env.CACHE_VERSION }}-${{ hashFiles('pyproject.toml', + 'requirements_test.txt', 'requirements_test_min.txt', + 'requirements_test_pre_commit.txt') }} + - name: Create Python virtual environment + if: steps.cache-venv.outputs.cache-hit != 'true' + run: | + python -m venv venv + . venv/bin/activate + python -m pip install --upgrade pip + pip install --upgrade --requirement requirements_test.txt + - name: Save Python virtual environment to cache + if: steps.cache-venv.outputs.cache-hit != 'true' + uses: actions/cache/save@v5.0.4 + with: + path: venv + key: >- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + env.KEY_PREFIX }}-${{ env.CACHE_VERSION }}-${{ hashFiles('pyproject.toml', + 'requirements_test.txt', 'requirements_test_min.txt', + 'requirements_test_pre_commit.txt') }} + - name: Get commit string + id: commitstring + run: | + . venv/bin/activate + python tests/primer/pyreverse_primer.py prepare --make-commit-string + output=$(python tests/primer/pyreverse_primer.py prepare --read-commit-string) + echo "commitstring=$output" >> $GITHUB_OUTPUT + - name: Restore projects cache + id: cache-projects + uses: actions/cache/restore@v5.0.4 + with: + path: tests/.pylint_primer_tests/ + key: >- + ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-${{ + steps.commitstring.outputs.commitstring }}-pyreverse-primer + - name: Regenerate cache + if: steps.cache-projects.outputs.cache-hit != 'true' + run: | + . venv/bin/activate + python tests/primer/pyreverse_primer.py prepare --clone + - name: Save projects cache + if: steps.cache-projects.outputs.cache-hit != 'true' + uses: actions/cache/save@v5.0.4 + with: + path: tests/.pylint_primer_tests/ + key: >- + ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-${{ + steps.commitstring.outputs.commitstring }}-pyreverse-primer + - name: Upload commit string + uses: actions/upload-artifact@v7.0.0 + with: + name: pyreverse_primer_commitstring + path: + tests/.pyreverse_primer_tests/commit_string_${{ env.DEFAULT_PYTHON }}.txt + - name: Run pyreverse primer + run: | + . venv/bin/activate + pip install . --no-deps + python tests/primer/pyreverse_primer.py run --type=main + - name: Upload output + uses: actions/upload-artifact@v7.0.0 + with: + name: pyreverse_primer_output_main + path: + tests/.pyreverse_primer_tests/pyreverse_output_${{ env.DEFAULT_PYTHON + }}_main.txt diff --git a/.github/workflows/pyreverse_primer_run_pr.yaml b/.github/workflows/pyreverse_primer_run_pr.yaml new file mode 100644 index 0000000000..54cdf87574 --- /dev/null +++ b/.github/workflows/pyreverse_primer_run_pr.yaml @@ -0,0 +1,175 @@ +name: Pyreverse Primer / Run + +on: + pull_request: + types: [opened, synchronize, labeled, unlabeled, reopened] + paths: + - "pylint/pyreverse/**" + - "pylint/testutils/_primer/**" + - "tests/primer/pyreverse_primer.py" + - "tests/primer/test_primer_stdlib.py" + - "tests/pyreverse/**" + - "requirements*" + - ".github/workflows/pyreverse_primer*.yaml" + - "!.github/workflows/pyreverse_primer_run_main.yaml" + - "!.github/workflows/pyreverse_primer_comment.yaml" + - "!tests/primer/packages_to_prime.json" + - "!tests/primer/pyreverse_targets_to_prime.json" + branches: + - main + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + CACHE_VERSION: 1 + KEY_PREFIX: venv-pyreverse-primer + DEFAULT_PYTHON: "3.13" + +permissions: + contents: read + +jobs: + run-primer: + if: contains(github.event.pull_request.labels.*.name, 'pyreverse') + name: Run + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Check out code from GitHub + uses: actions/checkout@v6.0.2 + with: + fetch-depth: 0 + - name: Set up Python ${{ env.DEFAULT_PYTHON }} + id: python + uses: actions/setup-python@v6.2.0 + with: + python-version: ${{ env.DEFAULT_PYTHON }} + check-latest: true + - name: Restore Python virtual environment + id: cache-venv + uses: actions/cache/restore@v5.0.4 + with: + path: venv + key: >- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + env.KEY_PREFIX }}-${{ env.CACHE_VERSION }}-${{ hashFiles('pyproject.toml', + 'requirements_test.txt', 'requirements_test_min.txt', + 'requirements_test_pre_commit.txt') }} + - name: Create Python virtual environment + if: steps.cache-venv.outputs.cache-hit != 'true' + run: | + python -m venv venv + . venv/bin/activate + python -m pip install --upgrade pip + pip install --upgrade --requirement requirements_test.txt + - name: Save Python virtual environment + if: steps.cache-venv.outputs.cache-hit != 'true' + uses: actions/cache/save@v5.0.4 + with: + path: venv + key: >- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + env.KEY_PREFIX }}-${{ env.CACHE_VERSION }}-${{ hashFiles('pyproject.toml', + 'requirements_test.txt', 'requirements_test_min.txt', + 'requirements_test_pre_commit.txt') }} + - name: Download last main run info + id: download-main-run + uses: actions/github-script@v8.0.0 + with: + script: | + const fs = require('fs'); + const runs = await github.rest.actions.listWorkflowRuns({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: '.github/workflows/pyreverse_primer_run_main.yaml', + status: 'success' + }); + const lastRunMain = runs.data.workflow_runs.reduce((prev, current) => + prev.run_number > current.run_number ? prev : current + ); + const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: lastRunMain.id, + }); + for (const artifactName of ['pyreverse_primer_commitstring', 'pyreverse_primer_output_main']) { + const [artifact] = artifacts.data.artifacts.filter((candidate) => candidate.name === artifactName); + const downloaded = await github.rest.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: artifact.id, + archive_format: 'zip', + }); + fs.writeFileSync(`${artifactName}.zip`, Buffer.from(downloaded.data)); + } + return lastRunMain.head_sha; + - name: Copy and unzip the commit string + run: | + unzip pyreverse_primer_commitstring.zip + cp commit_string_${{ env.DEFAULT_PYTHON }}.txt tests/.pyreverse_primer_tests/commit_string_${{ env.DEFAULT_PYTHON }}.txt + - name: Unzip the output of main + run: | + unzip pyreverse_primer_output_main.zip + - name: Get commit string + id: commitstring + run: | + . venv/bin/activate + output=$(python tests/primer/pyreverse_primer.py prepare --read-commit-string) + echo "commitstring=$output" >> $GITHUB_OUTPUT + - name: Restore projects cache + id: cache-projects + uses: actions/cache/restore@v5.0.4 + with: + path: tests/.pylint_primer_tests/ + key: >- + ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-${{ + steps.commitstring.outputs.commitstring }}-pyreverse-primer + - name: Regenerate cache + if: steps.cache-projects.outputs.cache-hit != 'true' + run: | + . venv/bin/activate + python tests/primer/pyreverse_primer.py prepare --clone + - name: Save projects cache + if: steps.cache-projects.outputs.cache-hit != 'true' + uses: actions/cache/save@v5.0.4 + with: + path: tests/.pylint_primer_tests/ + key: >- + ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-${{ + steps.commitstring.outputs.commitstring }}-pyreverse-primer + - name: Check cache + run: | + . venv/bin/activate + python tests/primer/pyreverse_primer.py prepare --check + - name: Pull main + run: | + git config user.email "primer@example.com" + git config user.name "Pylint Primer" + git pull origin ${{ steps.download-main-run.outputs.result }} --no-edit --no-commit --no-rebase + - name: Run pyreverse primer + run: | + . venv/bin/activate + pip install . --no-deps + python tests/primer/pyreverse_primer.py run --type=pr + - name: Upload output of PR + uses: actions/upload-artifact@v7.0.0 + with: + name: pyreverse_primer_output_pr + path: + tests/.pyreverse_primer_tests/pyreverse_output_${{ env.DEFAULT_PYTHON + }}_pr.txt + - name: Upload output of main + uses: actions/upload-artifact@v7.0.0 + with: + name: pyreverse_primer_output_main + path: pyreverse_output_${{ env.DEFAULT_PYTHON }}_main.txt + - name: Save PR number + run: | + echo ${{ github.event.pull_request.number }} | tee pr_number.txt + - name: Upload PR number + uses: actions/upload-artifact@v7.0.0 + with: + name: pr_number + path: pr_number.txt From 381415feeaee467eeabd5fd1624d3cb5b69955ef Mon Sep 17 00:00:00 2001 From: Julfried Date: Mon, 13 Apr 2026 09:13:49 +0200 Subject: [PATCH 06/12] Rename testfiles --- .../{expected.md => expected_comment.md} | 0 .../method_removed/expected_comment.md | 31 ++++++++++++++++ .../multi_target_mixed/expected_comment.md | 31 ++++++++++++++++ .../{expected.md => expected_comment.md} | 0 ...cated.md => expected_comment_truncated.md} | 0 .../{expected.md => expected_comment.md} | 0 .../relationship_added/expected_comment.md | 35 +++++++++++++++++++ .../_primer/test_pyreverse_primer.py | 4 +-- 8 files changed, 99 insertions(+), 2 deletions(-) rename tests/testutils/_primer/pyreverse_cases/changed/{expected.md => expected_comment.md} (100%) create mode 100644 tests/testutils/_primer/pyreverse_cases/method_removed/expected_comment.md create mode 100644 tests/testutils/_primer/pyreverse_cases/multi_target_mixed/expected_comment.md rename tests/testutils/_primer/pyreverse_cases/multiple_targets/{expected.md => expected_comment.md} (100%) rename tests/testutils/_primer/pyreverse_cases/multiple_targets/{expected_truncated.md => expected_comment_truncated.md} (100%) rename tests/testutils/_primer/pyreverse_cases/no_change/{expected.md => expected_comment.md} (100%) create mode 100644 tests/testutils/_primer/pyreverse_cases/relationship_added/expected_comment.md diff --git a/tests/testutils/_primer/pyreverse_cases/changed/expected.md b/tests/testutils/_primer/pyreverse_cases/changed/expected_comment.md similarity index 100% rename from tests/testutils/_primer/pyreverse_cases/changed/expected.md rename to tests/testutils/_primer/pyreverse_cases/changed/expected_comment.md diff --git a/tests/testutils/_primer/pyreverse_cases/method_removed/expected_comment.md b/tests/testutils/_primer/pyreverse_cases/method_removed/expected_comment.md new file mode 100644 index 0000000000..caffe23c84 --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/method_removed/expected_comment.md @@ -0,0 +1,31 @@ + +🤖 **Effect of this PR on tracked pyreverse diagrams:** 🤖 + +**Effect on `ClassDef` in [astroid](https://github.com/pylint-dev/astroid):** + +
+Diagram diff + +```diff +--- main ++++ pr +@@ -1,5 +1,3 @@ + classDiagram + class ClassDef { +- infer() +- do_normalize_attributes() + } +``` +
+ +
+Rendered diagram + +```mermaid +classDiagram + class ClassDef { + } +``` +
+ +*This comment was generated for commit deadbeef* diff --git a/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/expected_comment.md b/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/expected_comment.md new file mode 100644 index 0000000000..338f1d093b --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/expected_comment.md @@ -0,0 +1,31 @@ + +🤖 **Effect of this PR on tracked pyreverse diagrams:** 🤖 + +**Effect on `ClassDef` in [astroid](https://github.com/pylint-dev/astroid):** + +
+Diagram diff + +```diff +--- main ++++ pr +@@ -1,3 +1,4 @@ + classDiagram + class ClassDef { ++ new_method() + } +``` +
+ +
+Rendered diagram + +```mermaid +classDiagram + class ClassDef { + new_method() + } +``` +
+ +*This comment was generated for commit deadbeef* diff --git a/tests/testutils/_primer/pyreverse_cases/multiple_targets/expected.md b/tests/testutils/_primer/pyreverse_cases/multiple_targets/expected_comment.md similarity index 100% rename from tests/testutils/_primer/pyreverse_cases/multiple_targets/expected.md rename to tests/testutils/_primer/pyreverse_cases/multiple_targets/expected_comment.md diff --git a/tests/testutils/_primer/pyreverse_cases/multiple_targets/expected_truncated.md b/tests/testutils/_primer/pyreverse_cases/multiple_targets/expected_comment_truncated.md similarity index 100% rename from tests/testutils/_primer/pyreverse_cases/multiple_targets/expected_truncated.md rename to tests/testutils/_primer/pyreverse_cases/multiple_targets/expected_comment_truncated.md diff --git a/tests/testutils/_primer/pyreverse_cases/no_change/expected.md b/tests/testutils/_primer/pyreverse_cases/no_change/expected_comment.md similarity index 100% rename from tests/testutils/_primer/pyreverse_cases/no_change/expected.md rename to tests/testutils/_primer/pyreverse_cases/no_change/expected_comment.md diff --git a/tests/testutils/_primer/pyreverse_cases/relationship_added/expected_comment.md b/tests/testutils/_primer/pyreverse_cases/relationship_added/expected_comment.md new file mode 100644 index 0000000000..bcd1791c6b --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/relationship_added/expected_comment.md @@ -0,0 +1,35 @@ + +🤖 **Effect of this PR on tracked pyreverse diagrams:** 🤖 + +**Effect on `ClassDef` in [astroid](https://github.com/pylint-dev/astroid):** + +
+Diagram diff + +```diff +--- main ++++ pr +@@ -1,3 +1,6 @@ + classDiagram + class ClassDef { + } ++ class NodeNG { ++ } ++ ClassDef --|> NodeNG +``` +
+ +
+Rendered diagram + +```mermaid +classDiagram + class ClassDef { + } + class NodeNG { + } + ClassDef --|> NodeNG +``` +
+ +*This comment was generated for commit deadbeef* diff --git a/tests/testutils/_primer/test_pyreverse_primer.py b/tests/testutils/_primer/test_pyreverse_primer.py index 99cb1ab9b7..578600bc9b 100644 --- a/tests/testutils/_primer/test_pyreverse_primer.py +++ b/tests/testutils/_primer/test_pyreverse_primer.py @@ -56,7 +56,7 @@ def test_pyreverse_primer_launch_bad_args( ], ) def test_compare(directory: Path, tmp_path: Path) -> None: - expected_file = directory / "expected.md" + expected_file = directory / "expected_comment.md" with patch( "sys.argv", [ @@ -95,7 +95,7 @@ def test_truncated_compare(tmp_path: Path) -> None: PYREVERSE_TARGETS_TO_PRIME_PATH, ).run() content = (tmp_path / "comment.txt").read_text(encoding="utf-8") - expected = (directory / "expected_truncated.md").read_text(encoding="utf-8") + expected = (directory / "expected_comment_truncated.md").read_text(encoding="utf-8") assert content == expected.rstrip("\n") assert len(content) < 525 From b367c3f217ea0f3100fdc149e31d1327c84d9b1d Mon Sep 17 00:00:00 2001 From: Julfried Date: Mon, 13 Apr 2026 10:26:56 +0200 Subject: [PATCH 07/12] testfiles directly readable as mermaid files instead of json --- .../_primer/pyreverse_cases/changed/main.json | 6 --- .../pyreverse_cases/changed/main_classdef.mmd | 3 ++ .../_primer/pyreverse_cases/changed/pr.json | 6 --- .../pyreverse_cases/changed/pr_classdef.mmd | 4 ++ .../method_removed/main_classdef.mmd | 5 +++ .../method_removed/pr_classdef.mmd | 3 ++ .../multi_target_mixed/main_classdef.mmd | 3 ++ .../multi_target_mixed/main_functiondef.mmd | 3 ++ .../multi_target_mixed/pr_classdef.mmd | 4 ++ .../multi_target_mixed/pr_functiondef.mmd | 3 ++ .../multiple_targets/main.json | 10 ----- .../multiple_targets/main_classdef.mmd | 3 ++ .../multiple_targets/main_functiondef.mmd | 3 ++ .../pyreverse_cases/multiple_targets/pr.json | 10 ----- .../multiple_targets/pr_classdef.mmd | 4 ++ .../multiple_targets/pr_functiondef.mmd | 4 ++ .../pyreverse_cases/no_change/main.json | 6 --- .../no_change/main_classdef.mmd | 3 ++ .../_primer/pyreverse_cases/no_change/pr.json | 6 --- .../pyreverse_cases/no_change/pr_classdef.mmd | 3 ++ .../relationship_added/main_classdef.mmd | 3 ++ .../relationship_added/pr_classdef.mmd | 6 +++ .../_primer/test_pyreverse_primer.py | 45 +++++++++++++++---- 23 files changed, 94 insertions(+), 52 deletions(-) delete mode 100644 tests/testutils/_primer/pyreverse_cases/changed/main.json create mode 100644 tests/testutils/_primer/pyreverse_cases/changed/main_classdef.mmd delete mode 100644 tests/testutils/_primer/pyreverse_cases/changed/pr.json create mode 100644 tests/testutils/_primer/pyreverse_cases/changed/pr_classdef.mmd create mode 100644 tests/testutils/_primer/pyreverse_cases/method_removed/main_classdef.mmd create mode 100644 tests/testutils/_primer/pyreverse_cases/method_removed/pr_classdef.mmd create mode 100644 tests/testutils/_primer/pyreverse_cases/multi_target_mixed/main_classdef.mmd create mode 100644 tests/testutils/_primer/pyreverse_cases/multi_target_mixed/main_functiondef.mmd create mode 100644 tests/testutils/_primer/pyreverse_cases/multi_target_mixed/pr_classdef.mmd create mode 100644 tests/testutils/_primer/pyreverse_cases/multi_target_mixed/pr_functiondef.mmd delete mode 100644 tests/testutils/_primer/pyreverse_cases/multiple_targets/main.json create mode 100644 tests/testutils/_primer/pyreverse_cases/multiple_targets/main_classdef.mmd create mode 100644 tests/testutils/_primer/pyreverse_cases/multiple_targets/main_functiondef.mmd delete mode 100644 tests/testutils/_primer/pyreverse_cases/multiple_targets/pr.json create mode 100644 tests/testutils/_primer/pyreverse_cases/multiple_targets/pr_classdef.mmd create mode 100644 tests/testutils/_primer/pyreverse_cases/multiple_targets/pr_functiondef.mmd delete mode 100644 tests/testutils/_primer/pyreverse_cases/no_change/main.json create mode 100644 tests/testutils/_primer/pyreverse_cases/no_change/main_classdef.mmd delete mode 100644 tests/testutils/_primer/pyreverse_cases/no_change/pr.json create mode 100644 tests/testutils/_primer/pyreverse_cases/no_change/pr_classdef.mmd create mode 100644 tests/testutils/_primer/pyreverse_cases/relationship_added/main_classdef.mmd create mode 100644 tests/testutils/_primer/pyreverse_cases/relationship_added/pr_classdef.mmd diff --git a/tests/testutils/_primer/pyreverse_cases/changed/main.json b/tests/testutils/_primer/pyreverse_cases/changed/main.json deleted file mode 100644 index 5b31907abd..0000000000 --- a/tests/testutils/_primer/pyreverse_cases/changed/main.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "classdef": { - "commit": "1234567890abcdef", - "diagram": "classDiagram\n class ClassDef {\n }\n" - } -} diff --git a/tests/testutils/_primer/pyreverse_cases/changed/main_classdef.mmd b/tests/testutils/_primer/pyreverse_cases/changed/main_classdef.mmd new file mode 100644 index 0000000000..042c73aeea --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/changed/main_classdef.mmd @@ -0,0 +1,3 @@ +classDiagram + class ClassDef { + } diff --git a/tests/testutils/_primer/pyreverse_cases/changed/pr.json b/tests/testutils/_primer/pyreverse_cases/changed/pr.json deleted file mode 100644 index 46f980e761..0000000000 --- a/tests/testutils/_primer/pyreverse_cases/changed/pr.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "classdef": { - "commit": "1234567890abcdef", - "diagram": "classDiagram\n class ClassDef {\n infer()\n }\n" - } -} diff --git a/tests/testutils/_primer/pyreverse_cases/changed/pr_classdef.mmd b/tests/testutils/_primer/pyreverse_cases/changed/pr_classdef.mmd new file mode 100644 index 0000000000..1d1bc00151 --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/changed/pr_classdef.mmd @@ -0,0 +1,4 @@ +classDiagram + class ClassDef { + infer() + } diff --git a/tests/testutils/_primer/pyreverse_cases/method_removed/main_classdef.mmd b/tests/testutils/_primer/pyreverse_cases/method_removed/main_classdef.mmd new file mode 100644 index 0000000000..837f085e0f --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/method_removed/main_classdef.mmd @@ -0,0 +1,5 @@ +classDiagram + class ClassDef { + infer() + do_normalize_attributes() + } diff --git a/tests/testutils/_primer/pyreverse_cases/method_removed/pr_classdef.mmd b/tests/testutils/_primer/pyreverse_cases/method_removed/pr_classdef.mmd new file mode 100644 index 0000000000..042c73aeea --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/method_removed/pr_classdef.mmd @@ -0,0 +1,3 @@ +classDiagram + class ClassDef { + } diff --git a/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/main_classdef.mmd b/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/main_classdef.mmd new file mode 100644 index 0000000000..042c73aeea --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/main_classdef.mmd @@ -0,0 +1,3 @@ +classDiagram + class ClassDef { + } diff --git a/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/main_functiondef.mmd b/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/main_functiondef.mmd new file mode 100644 index 0000000000..c0e14f62fb --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/main_functiondef.mmd @@ -0,0 +1,3 @@ +classDiagram + class FunctionDef { + } diff --git a/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/pr_classdef.mmd b/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/pr_classdef.mmd new file mode 100644 index 0000000000..bb3c922143 --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/pr_classdef.mmd @@ -0,0 +1,4 @@ +classDiagram + class ClassDef { + new_method() + } diff --git a/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/pr_functiondef.mmd b/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/pr_functiondef.mmd new file mode 100644 index 0000000000..c0e14f62fb --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/pr_functiondef.mmd @@ -0,0 +1,3 @@ +classDiagram + class FunctionDef { + } diff --git a/tests/testutils/_primer/pyreverse_cases/multiple_targets/main.json b/tests/testutils/_primer/pyreverse_cases/multiple_targets/main.json deleted file mode 100644 index 0563f4bf0d..0000000000 --- a/tests/testutils/_primer/pyreverse_cases/multiple_targets/main.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "classdef": { - "commit": "1234567890abcdef", - "diagram": "classDiagram\n class ClassDef {\n }\n" - }, - "functiondef": { - "commit": "1234567890abcdef", - "diagram": "classDiagram\n class FunctionDef {\n }\n" - } -} diff --git a/tests/testutils/_primer/pyreverse_cases/multiple_targets/main_classdef.mmd b/tests/testutils/_primer/pyreverse_cases/multiple_targets/main_classdef.mmd new file mode 100644 index 0000000000..042c73aeea --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/multiple_targets/main_classdef.mmd @@ -0,0 +1,3 @@ +classDiagram + class ClassDef { + } diff --git a/tests/testutils/_primer/pyreverse_cases/multiple_targets/main_functiondef.mmd b/tests/testutils/_primer/pyreverse_cases/multiple_targets/main_functiondef.mmd new file mode 100644 index 0000000000..c0e14f62fb --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/multiple_targets/main_functiondef.mmd @@ -0,0 +1,3 @@ +classDiagram + class FunctionDef { + } diff --git a/tests/testutils/_primer/pyreverse_cases/multiple_targets/pr.json b/tests/testutils/_primer/pyreverse_cases/multiple_targets/pr.json deleted file mode 100644 index acfb2363f6..0000000000 --- a/tests/testutils/_primer/pyreverse_cases/multiple_targets/pr.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "classdef": { - "commit": "1234567890abcdef", - "diagram": "classDiagram\n class ClassDef {\n infer()\n }\n" - }, - "functiondef": { - "commit": "1234567890abcdef", - "diagram": "classDiagram\n class FunctionDef {\n args\n }\n" - } -} diff --git a/tests/testutils/_primer/pyreverse_cases/multiple_targets/pr_classdef.mmd b/tests/testutils/_primer/pyreverse_cases/multiple_targets/pr_classdef.mmd new file mode 100644 index 0000000000..1d1bc00151 --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/multiple_targets/pr_classdef.mmd @@ -0,0 +1,4 @@ +classDiagram + class ClassDef { + infer() + } diff --git a/tests/testutils/_primer/pyreverse_cases/multiple_targets/pr_functiondef.mmd b/tests/testutils/_primer/pyreverse_cases/multiple_targets/pr_functiondef.mmd new file mode 100644 index 0000000000..1296a18042 --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/multiple_targets/pr_functiondef.mmd @@ -0,0 +1,4 @@ +classDiagram + class FunctionDef { + args + } diff --git a/tests/testutils/_primer/pyreverse_cases/no_change/main.json b/tests/testutils/_primer/pyreverse_cases/no_change/main.json deleted file mode 100644 index 5b31907abd..0000000000 --- a/tests/testutils/_primer/pyreverse_cases/no_change/main.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "classdef": { - "commit": "1234567890abcdef", - "diagram": "classDiagram\n class ClassDef {\n }\n" - } -} diff --git a/tests/testutils/_primer/pyreverse_cases/no_change/main_classdef.mmd b/tests/testutils/_primer/pyreverse_cases/no_change/main_classdef.mmd new file mode 100644 index 0000000000..042c73aeea --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/no_change/main_classdef.mmd @@ -0,0 +1,3 @@ +classDiagram + class ClassDef { + } diff --git a/tests/testutils/_primer/pyreverse_cases/no_change/pr.json b/tests/testutils/_primer/pyreverse_cases/no_change/pr.json deleted file mode 100644 index 5b31907abd..0000000000 --- a/tests/testutils/_primer/pyreverse_cases/no_change/pr.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "classdef": { - "commit": "1234567890abcdef", - "diagram": "classDiagram\n class ClassDef {\n }\n" - } -} diff --git a/tests/testutils/_primer/pyreverse_cases/no_change/pr_classdef.mmd b/tests/testutils/_primer/pyreverse_cases/no_change/pr_classdef.mmd new file mode 100644 index 0000000000..042c73aeea --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/no_change/pr_classdef.mmd @@ -0,0 +1,3 @@ +classDiagram + class ClassDef { + } diff --git a/tests/testutils/_primer/pyreverse_cases/relationship_added/main_classdef.mmd b/tests/testutils/_primer/pyreverse_cases/relationship_added/main_classdef.mmd new file mode 100644 index 0000000000..042c73aeea --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/relationship_added/main_classdef.mmd @@ -0,0 +1,3 @@ +classDiagram + class ClassDef { + } diff --git a/tests/testutils/_primer/pyreverse_cases/relationship_added/pr_classdef.mmd b/tests/testutils/_primer/pyreverse_cases/relationship_added/pr_classdef.mmd new file mode 100644 index 0000000000..94b8f5f3ef --- /dev/null +++ b/tests/testutils/_primer/pyreverse_cases/relationship_added/pr_classdef.mmd @@ -0,0 +1,6 @@ +classDiagram + class ClassDef { + } + class NodeNG { + } + ClassDef --|> NodeNG diff --git a/tests/testutils/_primer/test_pyreverse_primer.py b/tests/testutils/_primer/test_pyreverse_primer.py index 578600bc9b..80f770c882 100644 --- a/tests/testutils/_primer/test_pyreverse_primer.py +++ b/tests/testutils/_primer/test_pyreverse_primer.py @@ -30,6 +30,24 @@ "--commit=deadbeef", ] +COMMIT = "1234567890abcdef" + + +def _load_fixture(directory: Path, filename: str) -> dict: + data: dict[str, dict[str, str]] = {} + path = directory / filename + if path.exists(): + raw = path.read_text(encoding="utf-8") + data = json.loads(raw) if raw.strip() else {} + prefix = filename.removesuffix(".json") + for mmd_file in directory.glob(f"{prefix}_*.mmd"): + target_name = mmd_file.stem.removeprefix(f"{prefix}_") + if target_name not in data: + data[target_name] = {} + data[target_name]["commit"] = COMMIT + data[target_name]["diagram"] = mmd_file.read_text(encoding="utf-8") + return data + @pytest.mark.parametrize("args", [[], ["wrong_command"]]) def test_pyreverse_primer_launch_bad_args( @@ -56,13 +74,18 @@ def test_pyreverse_primer_launch_bad_args( ], ) def test_compare(directory: Path, tmp_path: Path) -> None: - expected_file = directory / "expected_comment.md" + base_data = _load_fixture(directory, "main.json") + new_data = _load_fixture(directory, "pr.json") + base_file = tmp_path / "main.json" + new_file = tmp_path / "pr.json" + base_file.write_text(json.dumps(base_data), encoding="utf-8") + new_file.write_text(json.dumps(new_data), encoding="utf-8") with patch( "sys.argv", [ *DEFAULT_ARGS, - f"--base-file={directory / 'main.json'}", - f"--new-file={directory / 'pr.json'}", + f"--base-file={base_file}", + f"--new-file={new_file}", ], ): PyreversePrimer( @@ -71,12 +94,18 @@ def test_compare(directory: Path, tmp_path: Path) -> None: PYREVERSE_TARGETS_TO_PRIME_PATH, ).run() content = (tmp_path / "comment.txt").read_text(encoding="utf-8") - expected = expected_file.read_text(encoding="utf-8") + expected = (directory / "expected_comment.md").read_text(encoding="utf-8") assert content == expected.rstrip("\n") def test_truncated_compare(tmp_path: Path) -> None: directory = CASES_PATH / "multiple_targets" + base_data = _load_fixture(directory, "main.json") + new_data = _load_fixture(directory, "pr.json") + base_file = tmp_path / "main.json" + new_file = tmp_path / "pr.json" + base_file.write_text(json.dumps(base_data), encoding="utf-8") + new_file.write_text(json.dumps(new_data), encoding="utf-8") with patch( "pylint.testutils._primer.pyreverse_primer_compare_command.MAX_GITHUB_COMMENT_LENGTH", 525, @@ -85,8 +114,8 @@ def test_truncated_compare(tmp_path: Path) -> None: "sys.argv", [ *DEFAULT_ARGS, - f"--base-file={directory / 'main.json'}", - f"--new-file={directory / 'pr.json'}", + f"--base-file={base_file}", + f"--new-file={new_file}", ], ): PyreversePrimer( @@ -102,7 +131,7 @@ def test_truncated_compare(tmp_path: Path) -> None: def test_run_writes_output(tmp_path: Path) -> None: fake_repo = Mock() - fake_repo.head.object.hexsha = "1234567890abcdef" + fake_repo.head.object.hexsha = COMMIT def _render_diagram(*_: object, target_name: str) -> str: return f"classDiagram\n class {target_name} {{\n }}\n" @@ -140,6 +169,6 @@ def _render_diagram(*_: object, target_name: str) -> str: assert mock_render_target.call_count == 3 assert list(content) == ["classdef", "functiondef", "assignname"] assert content["classdef"] == { - "commit": "1234567890abcdef", + "commit": COMMIT, "diagram": "classDiagram\n class classdef {\n }\n", } From 6a21fcde527dd9e28940966562c2803e6ad1cf5a Mon Sep 17 00:00:00 2001 From: Julfried Date: Mon, 13 Apr 2026 10:27:04 +0200 Subject: [PATCH 08/12] Fix mypy --- tests/testutils/_primer/test_pyreverse_primer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testutils/_primer/test_pyreverse_primer.py b/tests/testutils/_primer/test_pyreverse_primer.py index 80f770c882..43197207d5 100644 --- a/tests/testutils/_primer/test_pyreverse_primer.py +++ b/tests/testutils/_primer/test_pyreverse_primer.py @@ -33,7 +33,7 @@ COMMIT = "1234567890abcdef" -def _load_fixture(directory: Path, filename: str) -> dict: +def _load_fixture(directory: Path, filename: str) -> dict[str, dict[str, str]]: data: dict[str, dict[str, str]] = {} path = directory / filename if path.exists(): From 0212704a2648349215c4304511e5229a925995ac Mon Sep 17 00:00:00 2001 From: Julfried Date: Fri, 17 Apr 2026 08:09:55 +0200 Subject: [PATCH 09/12] Make testcases more realistic in terms of diagram size --- .../changed/expected_comment.md | 78 ++++++++- .../pyreverse_cases/changed/main_classdef.mmd | 48 ++++++ .../pyreverse_cases/changed/pr_classdef.mmd | 53 +++++- .../method_removed/expected_comment.md | 74 ++++++++- .../method_removed/main_classdef.mmd | 54 +++++- .../method_removed/pr_classdef.mmd | 48 ++++++ .../multi_target_mixed/expected_comment.md | 78 ++++++++- .../multi_target_mixed/main_classdef.mmd | 48 ++++++ .../multi_target_mixed/main_functiondef.mmd | 35 ++++ .../multi_target_mixed/pr_classdef.mmd | 53 +++++- .../multi_target_mixed/pr_functiondef.mmd | 35 ++++ .../multiple_targets/expected_comment.md | 155 +++++++++++++++++- .../expected_comment_truncated.md | 14 +- .../multiple_targets/main_classdef.mmd | 48 ++++++ .../multiple_targets/main_functiondef.mmd | 35 ++++ .../multiple_targets/pr_classdef.mmd | 53 +++++- .../multiple_targets/pr_functiondef.mmd | 49 +++++- .../no_change/main_classdef.mmd | 48 ++++++ .../pyreverse_cases/no_change/pr_classdef.mmd | 48 ++++++ .../relationship_added/expected_comment.md | 108 +++++++++++- .../relationship_added/main_classdef.mmd | 48 ++++++ .../relationship_added/pr_classdef.mmd | 66 +++++++- 22 files changed, 1234 insertions(+), 42 deletions(-) diff --git a/tests/testutils/_primer/pyreverse_cases/changed/expected_comment.md b/tests/testutils/_primer/pyreverse_cases/changed/expected_comment.md index f24df96dff..457f84a4b6 100644 --- a/tests/testutils/_primer/pyreverse_cases/changed/expected_comment.md +++ b/tests/testutils/_primer/pyreverse_cases/changed/expected_comment.md @@ -9,11 +9,30 @@ ```diff --- main +++ pr -@@ -1,3 +1,4 @@ - classDiagram +@@ -29,12 +29,15 @@ + } class ClassDef { -+ infer() } ++ class Decorators { ++ } + BaseInstance --|> Proxy + Instance --|> BaseInstance + ClassModel --|> ObjectModel + InstanceModel --|> ObjectModel + FilterStmtsBaseNode --|> NodeNG + LookupMixIn --|> NodeNG ++ Decorators --|> NodeNG + Statement --|> NodeNG + LocalsDictNodeNG --|> LookupMixIn + ClassDef --|> FilterStmtsBaseNode +@@ -46,6 +49,7 @@ + InferenceContext --* ClassModel : context + ClassModel --* ClassDef : special_attributes + InstanceModel --* Instance : special_attributes ++ Decorators --o ClassDef : decorators + Union --o InferenceContext : boundnode + Instance --o InferenceContext : boundnode + InferenceContext --o InferenceContext : boundnode ``` @@ -22,9 +41,60 @@ ```mermaid classDiagram + class Union { + } + class BaseInstance { + } + class Instance { + } + class Proxy { + } + class CallContext { + } + class InferenceContext { + } + class ClassModel { + } + class InstanceModel { + } + class ObjectModel { + } + class FilterStmtsBaseNode { + } + class LookupMixIn { + } + class Statement { + } + class NodeNG { + } + class LocalsDictNodeNG { + } class ClassDef { - infer() } + class Decorators { + } + BaseInstance --|> Proxy + Instance --|> BaseInstance + ClassModel --|> ObjectModel + InstanceModel --|> ObjectModel + FilterStmtsBaseNode --|> NodeNG + LookupMixIn --|> NodeNG + Decorators --|> NodeNG + Statement --|> NodeNG + LocalsDictNodeNG --|> LookupMixIn + ClassDef --|> FilterStmtsBaseNode + ClassDef --|> Statement + ClassDef --|> LocalsDictNodeNG + BaseInstance --> ObjectModel : special_attributes + ClassDef --> NodeNG : instance_attrs + CallContext --* InferenceContext : callcontext + InferenceContext --* ClassModel : context + ClassModel --* ClassDef : special_attributes + InstanceModel --* Instance : special_attributes + Decorators --o ClassDef : decorators + Union --o InferenceContext : boundnode + Instance --o InferenceContext : boundnode + InferenceContext --o InferenceContext : boundnode ``` diff --git a/tests/testutils/_primer/pyreverse_cases/changed/main_classdef.mmd b/tests/testutils/_primer/pyreverse_cases/changed/main_classdef.mmd index 042c73aeea..70371d8c81 100644 --- a/tests/testutils/_primer/pyreverse_cases/changed/main_classdef.mmd +++ b/tests/testutils/_primer/pyreverse_cases/changed/main_classdef.mmd @@ -1,3 +1,51 @@ classDiagram + class Union { + } + class BaseInstance { + } + class Instance { + } + class Proxy { + } + class CallContext { + } + class InferenceContext { + } + class ClassModel { + } + class InstanceModel { + } + class ObjectModel { + } + class FilterStmtsBaseNode { + } + class LookupMixIn { + } + class Statement { + } + class NodeNG { + } + class LocalsDictNodeNG { + } class ClassDef { } + BaseInstance --|> Proxy + Instance --|> BaseInstance + ClassModel --|> ObjectModel + InstanceModel --|> ObjectModel + FilterStmtsBaseNode --|> NodeNG + LookupMixIn --|> NodeNG + Statement --|> NodeNG + LocalsDictNodeNG --|> LookupMixIn + ClassDef --|> FilterStmtsBaseNode + ClassDef --|> Statement + ClassDef --|> LocalsDictNodeNG + BaseInstance --> ObjectModel : special_attributes + ClassDef --> NodeNG : instance_attrs + CallContext --* InferenceContext : callcontext + InferenceContext --* ClassModel : context + ClassModel --* ClassDef : special_attributes + InstanceModel --* Instance : special_attributes + Union --o InferenceContext : boundnode + Instance --o InferenceContext : boundnode + InferenceContext --o InferenceContext : boundnode diff --git a/tests/testutils/_primer/pyreverse_cases/changed/pr_classdef.mmd b/tests/testutils/_primer/pyreverse_cases/changed/pr_classdef.mmd index 1d1bc00151..fe4327aa40 100644 --- a/tests/testutils/_primer/pyreverse_cases/changed/pr_classdef.mmd +++ b/tests/testutils/_primer/pyreverse_cases/changed/pr_classdef.mmd @@ -1,4 +1,55 @@ classDiagram + class Union { + } + class BaseInstance { + } + class Instance { + } + class Proxy { + } + class CallContext { + } + class InferenceContext { + } + class ClassModel { + } + class InstanceModel { + } + class ObjectModel { + } + class FilterStmtsBaseNode { + } + class LookupMixIn { + } + class Statement { + } + class NodeNG { + } + class LocalsDictNodeNG { + } class ClassDef { - infer() } + class Decorators { + } + BaseInstance --|> Proxy + Instance --|> BaseInstance + ClassModel --|> ObjectModel + InstanceModel --|> ObjectModel + FilterStmtsBaseNode --|> NodeNG + LookupMixIn --|> NodeNG + Decorators --|> NodeNG + Statement --|> NodeNG + LocalsDictNodeNG --|> LookupMixIn + ClassDef --|> FilterStmtsBaseNode + ClassDef --|> Statement + ClassDef --|> LocalsDictNodeNG + BaseInstance --> ObjectModel : special_attributes + ClassDef --> NodeNG : instance_attrs + CallContext --* InferenceContext : callcontext + InferenceContext --* ClassModel : context + ClassModel --* ClassDef : special_attributes + InstanceModel --* Instance : special_attributes + Decorators --o ClassDef : decorators + Union --o InferenceContext : boundnode + Instance --o InferenceContext : boundnode + InferenceContext --o InferenceContext : boundnode diff --git a/tests/testutils/_primer/pyreverse_cases/method_removed/expected_comment.md b/tests/testutils/_primer/pyreverse_cases/method_removed/expected_comment.md index caffe23c84..e78ac3b859 100644 --- a/tests/testutils/_primer/pyreverse_cases/method_removed/expected_comment.md +++ b/tests/testutils/_primer/pyreverse_cases/method_removed/expected_comment.md @@ -9,12 +9,30 @@ ```diff --- main +++ pr -@@ -1,5 +1,3 @@ - classDiagram +@@ -29,15 +29,12 @@ + } class ClassDef { -- infer() -- do_normalize_attributes() } +- class Decorators { +- } + BaseInstance --|> Proxy + Instance --|> BaseInstance + ClassModel --|> ObjectModel + InstanceModel --|> ObjectModel + FilterStmtsBaseNode --|> NodeNG + LookupMixIn --|> NodeNG +- Decorators --|> NodeNG + Statement --|> NodeNG + LocalsDictNodeNG --|> LookupMixIn + ClassDef --|> FilterStmtsBaseNode +@@ -49,7 +46,6 @@ + InferenceContext --* ClassModel : context + ClassModel --* ClassDef : special_attributes + InstanceModel --* Instance : special_attributes +- Decorators --o ClassDef : decorators + Union --o InferenceContext : boundnode + Instance --o InferenceContext : boundnode + InferenceContext --o InferenceContext : boundnode ``` @@ -23,8 +41,56 @@ ```mermaid classDiagram + class Union { + } + class BaseInstance { + } + class Instance { + } + class Proxy { + } + class CallContext { + } + class InferenceContext { + } + class ClassModel { + } + class InstanceModel { + } + class ObjectModel { + } + class FilterStmtsBaseNode { + } + class LookupMixIn { + } + class Statement { + } + class NodeNG { + } + class LocalsDictNodeNG { + } class ClassDef { } + BaseInstance --|> Proxy + Instance --|> BaseInstance + ClassModel --|> ObjectModel + InstanceModel --|> ObjectModel + FilterStmtsBaseNode --|> NodeNG + LookupMixIn --|> NodeNG + Statement --|> NodeNG + LocalsDictNodeNG --|> LookupMixIn + ClassDef --|> FilterStmtsBaseNode + ClassDef --|> Statement + ClassDef --|> LocalsDictNodeNG + BaseInstance --> ObjectModel : special_attributes + ClassDef --> NodeNG : instance_attrs + CallContext --* InferenceContext : callcontext + InferenceContext --* ClassModel : context + ClassModel --* ClassDef : special_attributes + InstanceModel --* Instance : special_attributes + Union --o InferenceContext : boundnode + Instance --o InferenceContext : boundnode + InferenceContext --o InferenceContext : boundnode ``` diff --git a/tests/testutils/_primer/pyreverse_cases/method_removed/main_classdef.mmd b/tests/testutils/_primer/pyreverse_cases/method_removed/main_classdef.mmd index 837f085e0f..fe4327aa40 100644 --- a/tests/testutils/_primer/pyreverse_cases/method_removed/main_classdef.mmd +++ b/tests/testutils/_primer/pyreverse_cases/method_removed/main_classdef.mmd @@ -1,5 +1,55 @@ classDiagram + class Union { + } + class BaseInstance { + } + class Instance { + } + class Proxy { + } + class CallContext { + } + class InferenceContext { + } + class ClassModel { + } + class InstanceModel { + } + class ObjectModel { + } + class FilterStmtsBaseNode { + } + class LookupMixIn { + } + class Statement { + } + class NodeNG { + } + class LocalsDictNodeNG { + } class ClassDef { - infer() - do_normalize_attributes() } + class Decorators { + } + BaseInstance --|> Proxy + Instance --|> BaseInstance + ClassModel --|> ObjectModel + InstanceModel --|> ObjectModel + FilterStmtsBaseNode --|> NodeNG + LookupMixIn --|> NodeNG + Decorators --|> NodeNG + Statement --|> NodeNG + LocalsDictNodeNG --|> LookupMixIn + ClassDef --|> FilterStmtsBaseNode + ClassDef --|> Statement + ClassDef --|> LocalsDictNodeNG + BaseInstance --> ObjectModel : special_attributes + ClassDef --> NodeNG : instance_attrs + CallContext --* InferenceContext : callcontext + InferenceContext --* ClassModel : context + ClassModel --* ClassDef : special_attributes + InstanceModel --* Instance : special_attributes + Decorators --o ClassDef : decorators + Union --o InferenceContext : boundnode + Instance --o InferenceContext : boundnode + InferenceContext --o InferenceContext : boundnode diff --git a/tests/testutils/_primer/pyreverse_cases/method_removed/pr_classdef.mmd b/tests/testutils/_primer/pyreverse_cases/method_removed/pr_classdef.mmd index 042c73aeea..70371d8c81 100644 --- a/tests/testutils/_primer/pyreverse_cases/method_removed/pr_classdef.mmd +++ b/tests/testutils/_primer/pyreverse_cases/method_removed/pr_classdef.mmd @@ -1,3 +1,51 @@ classDiagram + class Union { + } + class BaseInstance { + } + class Instance { + } + class Proxy { + } + class CallContext { + } + class InferenceContext { + } + class ClassModel { + } + class InstanceModel { + } + class ObjectModel { + } + class FilterStmtsBaseNode { + } + class LookupMixIn { + } + class Statement { + } + class NodeNG { + } + class LocalsDictNodeNG { + } class ClassDef { } + BaseInstance --|> Proxy + Instance --|> BaseInstance + ClassModel --|> ObjectModel + InstanceModel --|> ObjectModel + FilterStmtsBaseNode --|> NodeNG + LookupMixIn --|> NodeNG + Statement --|> NodeNG + LocalsDictNodeNG --|> LookupMixIn + ClassDef --|> FilterStmtsBaseNode + ClassDef --|> Statement + ClassDef --|> LocalsDictNodeNG + BaseInstance --> ObjectModel : special_attributes + ClassDef --> NodeNG : instance_attrs + CallContext --* InferenceContext : callcontext + InferenceContext --* ClassModel : context + ClassModel --* ClassDef : special_attributes + InstanceModel --* Instance : special_attributes + Union --o InferenceContext : boundnode + Instance --o InferenceContext : boundnode + InferenceContext --o InferenceContext : boundnode diff --git a/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/expected_comment.md b/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/expected_comment.md index 338f1d093b..457f84a4b6 100644 --- a/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/expected_comment.md +++ b/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/expected_comment.md @@ -9,11 +9,30 @@ ```diff --- main +++ pr -@@ -1,3 +1,4 @@ - classDiagram +@@ -29,12 +29,15 @@ + } class ClassDef { -+ new_method() } ++ class Decorators { ++ } + BaseInstance --|> Proxy + Instance --|> BaseInstance + ClassModel --|> ObjectModel + InstanceModel --|> ObjectModel + FilterStmtsBaseNode --|> NodeNG + LookupMixIn --|> NodeNG ++ Decorators --|> NodeNG + Statement --|> NodeNG + LocalsDictNodeNG --|> LookupMixIn + ClassDef --|> FilterStmtsBaseNode +@@ -46,6 +49,7 @@ + InferenceContext --* ClassModel : context + ClassModel --* ClassDef : special_attributes + InstanceModel --* Instance : special_attributes ++ Decorators --o ClassDef : decorators + Union --o InferenceContext : boundnode + Instance --o InferenceContext : boundnode + InferenceContext --o InferenceContext : boundnode ``` @@ -22,9 +41,60 @@ ```mermaid classDiagram + class Union { + } + class BaseInstance { + } + class Instance { + } + class Proxy { + } + class CallContext { + } + class InferenceContext { + } + class ClassModel { + } + class InstanceModel { + } + class ObjectModel { + } + class FilterStmtsBaseNode { + } + class LookupMixIn { + } + class Statement { + } + class NodeNG { + } + class LocalsDictNodeNG { + } class ClassDef { - new_method() } + class Decorators { + } + BaseInstance --|> Proxy + Instance --|> BaseInstance + ClassModel --|> ObjectModel + InstanceModel --|> ObjectModel + FilterStmtsBaseNode --|> NodeNG + LookupMixIn --|> NodeNG + Decorators --|> NodeNG + Statement --|> NodeNG + LocalsDictNodeNG --|> LookupMixIn + ClassDef --|> FilterStmtsBaseNode + ClassDef --|> Statement + ClassDef --|> LocalsDictNodeNG + BaseInstance --> ObjectModel : special_attributes + ClassDef --> NodeNG : instance_attrs + CallContext --* InferenceContext : callcontext + InferenceContext --* ClassModel : context + ClassModel --* ClassDef : special_attributes + InstanceModel --* Instance : special_attributes + Decorators --o ClassDef : decorators + Union --o InferenceContext : boundnode + Instance --o InferenceContext : boundnode + InferenceContext --o InferenceContext : boundnode ``` diff --git a/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/main_classdef.mmd b/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/main_classdef.mmd index 042c73aeea..70371d8c81 100644 --- a/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/main_classdef.mmd +++ b/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/main_classdef.mmd @@ -1,3 +1,51 @@ classDiagram + class Union { + } + class BaseInstance { + } + class Instance { + } + class Proxy { + } + class CallContext { + } + class InferenceContext { + } + class ClassModel { + } + class InstanceModel { + } + class ObjectModel { + } + class FilterStmtsBaseNode { + } + class LookupMixIn { + } + class Statement { + } + class NodeNG { + } + class LocalsDictNodeNG { + } class ClassDef { } + BaseInstance --|> Proxy + Instance --|> BaseInstance + ClassModel --|> ObjectModel + InstanceModel --|> ObjectModel + FilterStmtsBaseNode --|> NodeNG + LookupMixIn --|> NodeNG + Statement --|> NodeNG + LocalsDictNodeNG --|> LookupMixIn + ClassDef --|> FilterStmtsBaseNode + ClassDef --|> Statement + ClassDef --|> LocalsDictNodeNG + BaseInstance --> ObjectModel : special_attributes + ClassDef --> NodeNG : instance_attrs + CallContext --* InferenceContext : callcontext + InferenceContext --* ClassModel : context + ClassModel --* ClassDef : special_attributes + InstanceModel --* Instance : special_attributes + Union --o InferenceContext : boundnode + Instance --o InferenceContext : boundnode + InferenceContext --o InferenceContext : boundnode diff --git a/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/main_functiondef.mmd b/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/main_functiondef.mmd index c0e14f62fb..95dbcbfebc 100644 --- a/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/main_functiondef.mmd +++ b/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/main_functiondef.mmd @@ -1,3 +1,38 @@ classDiagram + class FunctionModel { + } + class ObjectModel { + } + class AssignTypeNode { + } + class FilterStmtsBaseNode { + } + class LookupMixIn { + } + class MultiLineBlockNode { + } + class Statement { + } + class Arguments { + } + class NodeNG { + } + class LocalsDictNodeNG { + } class FunctionDef { } + FunctionModel --|> ObjectModel + AssignTypeNode --|> NodeNG + FilterStmtsBaseNode --|> NodeNG + LookupMixIn --|> NodeNG + MultiLineBlockNode --|> NodeNG + Statement --|> NodeNG + Arguments --|> AssignTypeNode + LocalsDictNodeNG --|> LookupMixIn + FunctionDef --|> FilterStmtsBaseNode + FunctionDef --|> MultiLineBlockNode + FunctionDef --|> Statement + FunctionDef --|> LocalsDictNodeNG + FunctionModel --* FunctionDef : special_attributes + Arguments --o FunctionDef : args + NodeNG --o FunctionDef : body diff --git a/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/pr_classdef.mmd b/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/pr_classdef.mmd index bb3c922143..fe4327aa40 100644 --- a/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/pr_classdef.mmd +++ b/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/pr_classdef.mmd @@ -1,4 +1,55 @@ classDiagram + class Union { + } + class BaseInstance { + } + class Instance { + } + class Proxy { + } + class CallContext { + } + class InferenceContext { + } + class ClassModel { + } + class InstanceModel { + } + class ObjectModel { + } + class FilterStmtsBaseNode { + } + class LookupMixIn { + } + class Statement { + } + class NodeNG { + } + class LocalsDictNodeNG { + } class ClassDef { - new_method() } + class Decorators { + } + BaseInstance --|> Proxy + Instance --|> BaseInstance + ClassModel --|> ObjectModel + InstanceModel --|> ObjectModel + FilterStmtsBaseNode --|> NodeNG + LookupMixIn --|> NodeNG + Decorators --|> NodeNG + Statement --|> NodeNG + LocalsDictNodeNG --|> LookupMixIn + ClassDef --|> FilterStmtsBaseNode + ClassDef --|> Statement + ClassDef --|> LocalsDictNodeNG + BaseInstance --> ObjectModel : special_attributes + ClassDef --> NodeNG : instance_attrs + CallContext --* InferenceContext : callcontext + InferenceContext --* ClassModel : context + ClassModel --* ClassDef : special_attributes + InstanceModel --* Instance : special_attributes + Decorators --o ClassDef : decorators + Union --o InferenceContext : boundnode + Instance --o InferenceContext : boundnode + InferenceContext --o InferenceContext : boundnode diff --git a/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/pr_functiondef.mmd b/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/pr_functiondef.mmd index c0e14f62fb..95dbcbfebc 100644 --- a/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/pr_functiondef.mmd +++ b/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/pr_functiondef.mmd @@ -1,3 +1,38 @@ classDiagram + class FunctionModel { + } + class ObjectModel { + } + class AssignTypeNode { + } + class FilterStmtsBaseNode { + } + class LookupMixIn { + } + class MultiLineBlockNode { + } + class Statement { + } + class Arguments { + } + class NodeNG { + } + class LocalsDictNodeNG { + } class FunctionDef { } + FunctionModel --|> ObjectModel + AssignTypeNode --|> NodeNG + FilterStmtsBaseNode --|> NodeNG + LookupMixIn --|> NodeNG + MultiLineBlockNode --|> NodeNG + Statement --|> NodeNG + Arguments --|> AssignTypeNode + LocalsDictNodeNG --|> LookupMixIn + FunctionDef --|> FilterStmtsBaseNode + FunctionDef --|> MultiLineBlockNode + FunctionDef --|> Statement + FunctionDef --|> LocalsDictNodeNG + FunctionModel --* FunctionDef : special_attributes + Arguments --o FunctionDef : args + NodeNG --o FunctionDef : body diff --git a/tests/testutils/_primer/pyreverse_cases/multiple_targets/expected_comment.md b/tests/testutils/_primer/pyreverse_cases/multiple_targets/expected_comment.md index 8c0c231cd2..7df4e7efbd 100644 --- a/tests/testutils/_primer/pyreverse_cases/multiple_targets/expected_comment.md +++ b/tests/testutils/_primer/pyreverse_cases/multiple_targets/expected_comment.md @@ -9,11 +9,30 @@ ```diff --- main +++ pr -@@ -1,3 +1,4 @@ - classDiagram +@@ -29,12 +29,15 @@ + } class ClassDef { -+ infer() } ++ class Decorators { ++ } + BaseInstance --|> Proxy + Instance --|> BaseInstance + ClassModel --|> ObjectModel + InstanceModel --|> ObjectModel + FilterStmtsBaseNode --|> NodeNG + LookupMixIn --|> NodeNG ++ Decorators --|> NodeNG + Statement --|> NodeNG + LocalsDictNodeNG --|> LookupMixIn + ClassDef --|> FilterStmtsBaseNode +@@ -46,6 +49,7 @@ + InferenceContext --* ClassModel : context + ClassModel --* ClassDef : special_attributes + InstanceModel --* Instance : special_attributes ++ Decorators --o ClassDef : decorators + Union --o InferenceContext : boundnode + Instance --o InferenceContext : boundnode + InferenceContext --o InferenceContext : boundnode ``` @@ -22,9 +41,60 @@ ```mermaid classDiagram + class Union { + } + class BaseInstance { + } + class Instance { + } + class Proxy { + } + class CallContext { + } + class InferenceContext { + } + class ClassModel { + } + class InstanceModel { + } + class ObjectModel { + } + class FilterStmtsBaseNode { + } + class LookupMixIn { + } + class Statement { + } + class NodeNG { + } + class LocalsDictNodeNG { + } class ClassDef { - infer() } + class Decorators { + } + BaseInstance --|> Proxy + Instance --|> BaseInstance + ClassModel --|> ObjectModel + InstanceModel --|> ObjectModel + FilterStmtsBaseNode --|> NodeNG + LookupMixIn --|> NodeNG + Decorators --|> NodeNG + Statement --|> NodeNG + LocalsDictNodeNG --|> LookupMixIn + ClassDef --|> FilterStmtsBaseNode + ClassDef --|> Statement + ClassDef --|> LocalsDictNodeNG + BaseInstance --> ObjectModel : special_attributes + ClassDef --> NodeNG : instance_attrs + CallContext --* InferenceContext : callcontext + InferenceContext --* ClassModel : context + ClassModel --* ClassDef : special_attributes + InstanceModel --* Instance : special_attributes + Decorators --o ClassDef : decorators + Union --o InferenceContext : boundnode + Instance --o InferenceContext : boundnode + InferenceContext --o InferenceContext : boundnode ``` @@ -36,11 +106,33 @@ classDiagram ```diff --- main +++ pr -@@ -1,3 +1,4 @@ - classDiagram +@@ -21,6 +21,12 @@ + } class FunctionDef { -+ args } ++ class CallContext { ++ } ++ class ClassDef { ++ } ++ class InferenceContext { ++ } + FunctionModel --|> ObjectModel + AssignTypeNode --|> NodeNG + FilterStmtsBaseNode --|> NodeNG +@@ -33,6 +39,13 @@ + FunctionDef --|> MultiLineBlockNode + FunctionDef --|> Statement + FunctionDef --|> LocalsDictNodeNG ++ ClassDef --|> FilterStmtsBaseNode ++ ClassDef --|> Statement ++ ClassDef --|> LocalsDictNodeNG + FunctionModel --* FunctionDef : special_attributes ++ CallContext --* InferenceContext : callcontext ++ ClassDef --> NodeNG : instance_attrs + Arguments --o FunctionDef : args + NodeNG --o FunctionDef : body ++ ClassDef --o InferenceContext : boundnode ++ ClassDef --o FunctionDef : parent ``` @@ -49,9 +141,56 @@ classDiagram ```mermaid classDiagram + class FunctionModel { + } + class ObjectModel { + } + class AssignTypeNode { + } + class FilterStmtsBaseNode { + } + class LookupMixIn { + } + class MultiLineBlockNode { + } + class Statement { + } + class Arguments { + } + class NodeNG { + } + class LocalsDictNodeNG { + } class FunctionDef { - args } + class CallContext { + } + class ClassDef { + } + class InferenceContext { + } + FunctionModel --|> ObjectModel + AssignTypeNode --|> NodeNG + FilterStmtsBaseNode --|> NodeNG + LookupMixIn --|> NodeNG + MultiLineBlockNode --|> NodeNG + Statement --|> NodeNG + Arguments --|> AssignTypeNode + LocalsDictNodeNG --|> LookupMixIn + FunctionDef --|> FilterStmtsBaseNode + FunctionDef --|> MultiLineBlockNode + FunctionDef --|> Statement + FunctionDef --|> LocalsDictNodeNG + ClassDef --|> FilterStmtsBaseNode + ClassDef --|> Statement + ClassDef --|> LocalsDictNodeNG + FunctionModel --* FunctionDef : special_attributes + CallContext --* InferenceContext : callcontext + ClassDef --> NodeNG : instance_attrs + Arguments --o FunctionDef : args + NodeNG --o FunctionDef : body + ClassDef --o InferenceContext : boundnode + ClassDef --o FunctionDef : parent ``` diff --git a/tests/testutils/_primer/pyreverse_cases/multiple_targets/expected_comment_truncated.md b/tests/testutils/_primer/pyreverse_cases/multiple_targets/expected_comment_truncated.md index 3fa456e864..92ca367930 100644 --- a/tests/testutils/_primer/pyreverse_cases/multiple_targets/expected_comment_truncated.md +++ b/tests/testutils/_primer/pyreverse_cases/multiple_targets/expected_comment_truncated.md @@ -9,16 +9,14 @@ ```diff --- main +++ pr -@@ -1,3 +1,4 @@ - classDiagram +@@ -29,12 +29,15 @@ + } class ClassDef { -+ infer() } -``` - - -
-Rendered... ++ class Decorators { ++ } + BaseInstance --|> Proxy + Instance --|>...
*This comment was truncated because GitHub allows only 525 characters in a comment.* diff --git a/tests/testutils/_primer/pyreverse_cases/multiple_targets/main_classdef.mmd b/tests/testutils/_primer/pyreverse_cases/multiple_targets/main_classdef.mmd index 042c73aeea..70371d8c81 100644 --- a/tests/testutils/_primer/pyreverse_cases/multiple_targets/main_classdef.mmd +++ b/tests/testutils/_primer/pyreverse_cases/multiple_targets/main_classdef.mmd @@ -1,3 +1,51 @@ classDiagram + class Union { + } + class BaseInstance { + } + class Instance { + } + class Proxy { + } + class CallContext { + } + class InferenceContext { + } + class ClassModel { + } + class InstanceModel { + } + class ObjectModel { + } + class FilterStmtsBaseNode { + } + class LookupMixIn { + } + class Statement { + } + class NodeNG { + } + class LocalsDictNodeNG { + } class ClassDef { } + BaseInstance --|> Proxy + Instance --|> BaseInstance + ClassModel --|> ObjectModel + InstanceModel --|> ObjectModel + FilterStmtsBaseNode --|> NodeNG + LookupMixIn --|> NodeNG + Statement --|> NodeNG + LocalsDictNodeNG --|> LookupMixIn + ClassDef --|> FilterStmtsBaseNode + ClassDef --|> Statement + ClassDef --|> LocalsDictNodeNG + BaseInstance --> ObjectModel : special_attributes + ClassDef --> NodeNG : instance_attrs + CallContext --* InferenceContext : callcontext + InferenceContext --* ClassModel : context + ClassModel --* ClassDef : special_attributes + InstanceModel --* Instance : special_attributes + Union --o InferenceContext : boundnode + Instance --o InferenceContext : boundnode + InferenceContext --o InferenceContext : boundnode diff --git a/tests/testutils/_primer/pyreverse_cases/multiple_targets/main_functiondef.mmd b/tests/testutils/_primer/pyreverse_cases/multiple_targets/main_functiondef.mmd index c0e14f62fb..95dbcbfebc 100644 --- a/tests/testutils/_primer/pyreverse_cases/multiple_targets/main_functiondef.mmd +++ b/tests/testutils/_primer/pyreverse_cases/multiple_targets/main_functiondef.mmd @@ -1,3 +1,38 @@ classDiagram + class FunctionModel { + } + class ObjectModel { + } + class AssignTypeNode { + } + class FilterStmtsBaseNode { + } + class LookupMixIn { + } + class MultiLineBlockNode { + } + class Statement { + } + class Arguments { + } + class NodeNG { + } + class LocalsDictNodeNG { + } class FunctionDef { } + FunctionModel --|> ObjectModel + AssignTypeNode --|> NodeNG + FilterStmtsBaseNode --|> NodeNG + LookupMixIn --|> NodeNG + MultiLineBlockNode --|> NodeNG + Statement --|> NodeNG + Arguments --|> AssignTypeNode + LocalsDictNodeNG --|> LookupMixIn + FunctionDef --|> FilterStmtsBaseNode + FunctionDef --|> MultiLineBlockNode + FunctionDef --|> Statement + FunctionDef --|> LocalsDictNodeNG + FunctionModel --* FunctionDef : special_attributes + Arguments --o FunctionDef : args + NodeNG --o FunctionDef : body diff --git a/tests/testutils/_primer/pyreverse_cases/multiple_targets/pr_classdef.mmd b/tests/testutils/_primer/pyreverse_cases/multiple_targets/pr_classdef.mmd index 1d1bc00151..fe4327aa40 100644 --- a/tests/testutils/_primer/pyreverse_cases/multiple_targets/pr_classdef.mmd +++ b/tests/testutils/_primer/pyreverse_cases/multiple_targets/pr_classdef.mmd @@ -1,4 +1,55 @@ classDiagram + class Union { + } + class BaseInstance { + } + class Instance { + } + class Proxy { + } + class CallContext { + } + class InferenceContext { + } + class ClassModel { + } + class InstanceModel { + } + class ObjectModel { + } + class FilterStmtsBaseNode { + } + class LookupMixIn { + } + class Statement { + } + class NodeNG { + } + class LocalsDictNodeNG { + } class ClassDef { - infer() } + class Decorators { + } + BaseInstance --|> Proxy + Instance --|> BaseInstance + ClassModel --|> ObjectModel + InstanceModel --|> ObjectModel + FilterStmtsBaseNode --|> NodeNG + LookupMixIn --|> NodeNG + Decorators --|> NodeNG + Statement --|> NodeNG + LocalsDictNodeNG --|> LookupMixIn + ClassDef --|> FilterStmtsBaseNode + ClassDef --|> Statement + ClassDef --|> LocalsDictNodeNG + BaseInstance --> ObjectModel : special_attributes + ClassDef --> NodeNG : instance_attrs + CallContext --* InferenceContext : callcontext + InferenceContext --* ClassModel : context + ClassModel --* ClassDef : special_attributes + InstanceModel --* Instance : special_attributes + Decorators --o ClassDef : decorators + Union --o InferenceContext : boundnode + Instance --o InferenceContext : boundnode + InferenceContext --o InferenceContext : boundnode diff --git a/tests/testutils/_primer/pyreverse_cases/multiple_targets/pr_functiondef.mmd b/tests/testutils/_primer/pyreverse_cases/multiple_targets/pr_functiondef.mmd index 1296a18042..b11b48938a 100644 --- a/tests/testutils/_primer/pyreverse_cases/multiple_targets/pr_functiondef.mmd +++ b/tests/testutils/_primer/pyreverse_cases/multiple_targets/pr_functiondef.mmd @@ -1,4 +1,51 @@ classDiagram + class FunctionModel { + } + class ObjectModel { + } + class AssignTypeNode { + } + class FilterStmtsBaseNode { + } + class LookupMixIn { + } + class MultiLineBlockNode { + } + class Statement { + } + class Arguments { + } + class NodeNG { + } + class LocalsDictNodeNG { + } class FunctionDef { - args } + class CallContext { + } + class ClassDef { + } + class InferenceContext { + } + FunctionModel --|> ObjectModel + AssignTypeNode --|> NodeNG + FilterStmtsBaseNode --|> NodeNG + LookupMixIn --|> NodeNG + MultiLineBlockNode --|> NodeNG + Statement --|> NodeNG + Arguments --|> AssignTypeNode + LocalsDictNodeNG --|> LookupMixIn + FunctionDef --|> FilterStmtsBaseNode + FunctionDef --|> MultiLineBlockNode + FunctionDef --|> Statement + FunctionDef --|> LocalsDictNodeNG + ClassDef --|> FilterStmtsBaseNode + ClassDef --|> Statement + ClassDef --|> LocalsDictNodeNG + FunctionModel --* FunctionDef : special_attributes + CallContext --* InferenceContext : callcontext + ClassDef --> NodeNG : instance_attrs + Arguments --o FunctionDef : args + NodeNG --o FunctionDef : body + ClassDef --o InferenceContext : boundnode + ClassDef --o FunctionDef : parent diff --git a/tests/testutils/_primer/pyreverse_cases/no_change/main_classdef.mmd b/tests/testutils/_primer/pyreverse_cases/no_change/main_classdef.mmd index 042c73aeea..70371d8c81 100644 --- a/tests/testutils/_primer/pyreverse_cases/no_change/main_classdef.mmd +++ b/tests/testutils/_primer/pyreverse_cases/no_change/main_classdef.mmd @@ -1,3 +1,51 @@ classDiagram + class Union { + } + class BaseInstance { + } + class Instance { + } + class Proxy { + } + class CallContext { + } + class InferenceContext { + } + class ClassModel { + } + class InstanceModel { + } + class ObjectModel { + } + class FilterStmtsBaseNode { + } + class LookupMixIn { + } + class Statement { + } + class NodeNG { + } + class LocalsDictNodeNG { + } class ClassDef { } + BaseInstance --|> Proxy + Instance --|> BaseInstance + ClassModel --|> ObjectModel + InstanceModel --|> ObjectModel + FilterStmtsBaseNode --|> NodeNG + LookupMixIn --|> NodeNG + Statement --|> NodeNG + LocalsDictNodeNG --|> LookupMixIn + ClassDef --|> FilterStmtsBaseNode + ClassDef --|> Statement + ClassDef --|> LocalsDictNodeNG + BaseInstance --> ObjectModel : special_attributes + ClassDef --> NodeNG : instance_attrs + CallContext --* InferenceContext : callcontext + InferenceContext --* ClassModel : context + ClassModel --* ClassDef : special_attributes + InstanceModel --* Instance : special_attributes + Union --o InferenceContext : boundnode + Instance --o InferenceContext : boundnode + InferenceContext --o InferenceContext : boundnode diff --git a/tests/testutils/_primer/pyreverse_cases/no_change/pr_classdef.mmd b/tests/testutils/_primer/pyreverse_cases/no_change/pr_classdef.mmd index 042c73aeea..70371d8c81 100644 --- a/tests/testutils/_primer/pyreverse_cases/no_change/pr_classdef.mmd +++ b/tests/testutils/_primer/pyreverse_cases/no_change/pr_classdef.mmd @@ -1,3 +1,51 @@ classDiagram + class Union { + } + class BaseInstance { + } + class Instance { + } + class Proxy { + } + class CallContext { + } + class InferenceContext { + } + class ClassModel { + } + class InstanceModel { + } + class ObjectModel { + } + class FilterStmtsBaseNode { + } + class LookupMixIn { + } + class Statement { + } + class NodeNG { + } + class LocalsDictNodeNG { + } class ClassDef { } + BaseInstance --|> Proxy + Instance --|> BaseInstance + ClassModel --|> ObjectModel + InstanceModel --|> ObjectModel + FilterStmtsBaseNode --|> NodeNG + LookupMixIn --|> NodeNG + Statement --|> NodeNG + LocalsDictNodeNG --|> LookupMixIn + ClassDef --|> FilterStmtsBaseNode + ClassDef --|> Statement + ClassDef --|> LocalsDictNodeNG + BaseInstance --> ObjectModel : special_attributes + ClassDef --> NodeNG : instance_attrs + CallContext --* InferenceContext : callcontext + InferenceContext --* ClassModel : context + ClassModel --* ClassDef : special_attributes + InstanceModel --* Instance : special_attributes + Union --o InferenceContext : boundnode + Instance --o InferenceContext : boundnode + InferenceContext --o InferenceContext : boundnode diff --git a/tests/testutils/_primer/pyreverse_cases/relationship_added/expected_comment.md b/tests/testutils/_primer/pyreverse_cases/relationship_added/expected_comment.md index bcd1791c6b..1ec546c133 100644 --- a/tests/testutils/_primer/pyreverse_cases/relationship_added/expected_comment.md +++ b/tests/testutils/_primer/pyreverse_cases/relationship_added/expected_comment.md @@ -9,13 +9,47 @@ ```diff --- main +++ pr -@@ -1,3 +1,6 @@ - classDiagram +@@ -29,23 +29,40 @@ + } class ClassDef { } -+ class NodeNG { ++ class AssignTypeNode { ++ } ++ class Arguments { ++ } ++ class Decorators { + } -+ ClassDef --|> NodeNG ++ class FunctionDef { ++ } + BaseInstance --|> Proxy + Instance --|> BaseInstance + ClassModel --|> ObjectModel + InstanceModel --|> ObjectModel + FilterStmtsBaseNode --|> NodeNG + LookupMixIn --|> NodeNG ++ AssignTypeNode --|> NodeNG ++ Arguments --|> AssignTypeNode ++ Decorators --|> NodeNG + Statement --|> NodeNG + LocalsDictNodeNG --|> LookupMixIn + ClassDef --|> FilterStmtsBaseNode + ClassDef --|> Statement + ClassDef --|> LocalsDictNodeNG ++ FunctionDef --|> FilterStmtsBaseNode ++ FunctionDef --|> Statement ++ FunctionDef --|> LocalsDictNodeNG + BaseInstance --> ObjectModel : special_attributes + ClassDef --> NodeNG : instance_attrs + CallContext --* InferenceContext : callcontext + InferenceContext --* ClassModel : context + ClassModel --* ClassDef : special_attributes + InstanceModel --* Instance : special_attributes ++ ClassDef --* FunctionDef : methods ++ Arguments --o FunctionDef : args ++ Decorators --o ClassDef : decorators + Union --o InferenceContext : boundnode + Instance --o InferenceContext : boundnode + InferenceContext --o InferenceContext : boundnode ``` @@ -24,11 +58,73 @@ ```mermaid classDiagram - class ClassDef { + class Union { + } + class BaseInstance { + } + class Instance { + } + class Proxy { + } + class CallContext { + } + class InferenceContext { + } + class ClassModel { + } + class InstanceModel { + } + class ObjectModel { + } + class FilterStmtsBaseNode { + } + class LookupMixIn { + } + class Statement { } class NodeNG { } - ClassDef --|> NodeNG + class LocalsDictNodeNG { + } + class ClassDef { + } + class AssignTypeNode { + } + class Arguments { + } + class Decorators { + } + class FunctionDef { + } + BaseInstance --|> Proxy + Instance --|> BaseInstance + ClassModel --|> ObjectModel + InstanceModel --|> ObjectModel + FilterStmtsBaseNode --|> NodeNG + LookupMixIn --|> NodeNG + AssignTypeNode --|> NodeNG + Arguments --|> AssignTypeNode + Decorators --|> NodeNG + Statement --|> NodeNG + LocalsDictNodeNG --|> LookupMixIn + ClassDef --|> FilterStmtsBaseNode + ClassDef --|> Statement + ClassDef --|> LocalsDictNodeNG + FunctionDef --|> FilterStmtsBaseNode + FunctionDef --|> Statement + FunctionDef --|> LocalsDictNodeNG + BaseInstance --> ObjectModel : special_attributes + ClassDef --> NodeNG : instance_attrs + CallContext --* InferenceContext : callcontext + InferenceContext --* ClassModel : context + ClassModel --* ClassDef : special_attributes + InstanceModel --* Instance : special_attributes + ClassDef --* FunctionDef : methods + Arguments --o FunctionDef : args + Decorators --o ClassDef : decorators + Union --o InferenceContext : boundnode + Instance --o InferenceContext : boundnode + InferenceContext --o InferenceContext : boundnode ``` diff --git a/tests/testutils/_primer/pyreverse_cases/relationship_added/main_classdef.mmd b/tests/testutils/_primer/pyreverse_cases/relationship_added/main_classdef.mmd index 042c73aeea..70371d8c81 100644 --- a/tests/testutils/_primer/pyreverse_cases/relationship_added/main_classdef.mmd +++ b/tests/testutils/_primer/pyreverse_cases/relationship_added/main_classdef.mmd @@ -1,3 +1,51 @@ classDiagram + class Union { + } + class BaseInstance { + } + class Instance { + } + class Proxy { + } + class CallContext { + } + class InferenceContext { + } + class ClassModel { + } + class InstanceModel { + } + class ObjectModel { + } + class FilterStmtsBaseNode { + } + class LookupMixIn { + } + class Statement { + } + class NodeNG { + } + class LocalsDictNodeNG { + } class ClassDef { } + BaseInstance --|> Proxy + Instance --|> BaseInstance + ClassModel --|> ObjectModel + InstanceModel --|> ObjectModel + FilterStmtsBaseNode --|> NodeNG + LookupMixIn --|> NodeNG + Statement --|> NodeNG + LocalsDictNodeNG --|> LookupMixIn + ClassDef --|> FilterStmtsBaseNode + ClassDef --|> Statement + ClassDef --|> LocalsDictNodeNG + BaseInstance --> ObjectModel : special_attributes + ClassDef --> NodeNG : instance_attrs + CallContext --* InferenceContext : callcontext + InferenceContext --* ClassModel : context + ClassModel --* ClassDef : special_attributes + InstanceModel --* Instance : special_attributes + Union --o InferenceContext : boundnode + Instance --o InferenceContext : boundnode + InferenceContext --o InferenceContext : boundnode diff --git a/tests/testutils/_primer/pyreverse_cases/relationship_added/pr_classdef.mmd b/tests/testutils/_primer/pyreverse_cases/relationship_added/pr_classdef.mmd index 94b8f5f3ef..62e8068c15 100644 --- a/tests/testutils/_primer/pyreverse_cases/relationship_added/pr_classdef.mmd +++ b/tests/testutils/_primer/pyreverse_cases/relationship_added/pr_classdef.mmd @@ -1,6 +1,68 @@ classDiagram - class ClassDef { + class Union { + } + class BaseInstance { + } + class Instance { + } + class Proxy { + } + class CallContext { + } + class InferenceContext { + } + class ClassModel { + } + class InstanceModel { + } + class ObjectModel { + } + class FilterStmtsBaseNode { + } + class LookupMixIn { + } + class Statement { } class NodeNG { } - ClassDef --|> NodeNG + class LocalsDictNodeNG { + } + class ClassDef { + } + class AssignTypeNode { + } + class Arguments { + } + class Decorators { + } + class FunctionDef { + } + BaseInstance --|> Proxy + Instance --|> BaseInstance + ClassModel --|> ObjectModel + InstanceModel --|> ObjectModel + FilterStmtsBaseNode --|> NodeNG + LookupMixIn --|> NodeNG + AssignTypeNode --|> NodeNG + Arguments --|> AssignTypeNode + Decorators --|> NodeNG + Statement --|> NodeNG + LocalsDictNodeNG --|> LookupMixIn + ClassDef --|> FilterStmtsBaseNode + ClassDef --|> Statement + ClassDef --|> LocalsDictNodeNG + FunctionDef --|> FilterStmtsBaseNode + FunctionDef --|> Statement + FunctionDef --|> LocalsDictNodeNG + BaseInstance --> ObjectModel : special_attributes + ClassDef --> NodeNG : instance_attrs + CallContext --* InferenceContext : callcontext + InferenceContext --* ClassModel : context + ClassModel --* ClassDef : special_attributes + InstanceModel --* Instance : special_attributes + ClassDef --* FunctionDef : methods + Arguments --o FunctionDef : args + Decorators --o ClassDef : decorators + Union --o InferenceContext : boundnode + Instance --o InferenceContext : boundnode + InferenceContext --o InferenceContext : boundnode From f07db0a651a9281ea13b1d10993e13556e591c32 Mon Sep 17 00:00:00 2001 From: Julfried Date: Fri, 17 Apr 2026 08:20:02 +0200 Subject: [PATCH 10/12] Clarify diagram source --- pylint/testutils/_primer/pyreverse_primer_compare_command.py | 2 +- .../_primer/pyreverse_cases/changed/expected_comment.md | 2 +- .../pyreverse_cases/method_removed/expected_comment.md | 2 +- .../pyreverse_cases/multi_target_mixed/expected_comment.md | 2 +- .../pyreverse_cases/multiple_targets/expected_comment.md | 4 ++-- .../pyreverse_cases/relationship_added/expected_comment.md | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pylint/testutils/_primer/pyreverse_primer_compare_command.py b/pylint/testutils/_primer/pyreverse_primer_compare_command.py index 29c9c4cddc..15f5a7a80a 100644 --- a/pylint/testutils/_primer/pyreverse_primer_compare_command.py +++ b/pylint/testutils/_primer/pyreverse_primer_compare_command.py @@ -73,7 +73,7 @@ def _create_comment_for_target( "
\nDiagram diff\n\n" f"```diff\n{diff}```\n" "
\n\n" - "
\nRendered diagram\n\n" + "
\nRendered diagram after this change\n\n" f"```mermaid\n{new_target_data['diagram'].rstrip()}\n```\n" "
\n\n" ) diff --git a/tests/testutils/_primer/pyreverse_cases/changed/expected_comment.md b/tests/testutils/_primer/pyreverse_cases/changed/expected_comment.md index 457f84a4b6..6f099c561f 100644 --- a/tests/testutils/_primer/pyreverse_cases/changed/expected_comment.md +++ b/tests/testutils/_primer/pyreverse_cases/changed/expected_comment.md @@ -37,7 +37,7 @@
-Rendered diagram +Rendered diagram after this change ```mermaid classDiagram diff --git a/tests/testutils/_primer/pyreverse_cases/method_removed/expected_comment.md b/tests/testutils/_primer/pyreverse_cases/method_removed/expected_comment.md index e78ac3b859..9e2fbda977 100644 --- a/tests/testutils/_primer/pyreverse_cases/method_removed/expected_comment.md +++ b/tests/testutils/_primer/pyreverse_cases/method_removed/expected_comment.md @@ -37,7 +37,7 @@
-Rendered diagram +Rendered diagram after this change ```mermaid classDiagram diff --git a/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/expected_comment.md b/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/expected_comment.md index 457f84a4b6..6f099c561f 100644 --- a/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/expected_comment.md +++ b/tests/testutils/_primer/pyreverse_cases/multi_target_mixed/expected_comment.md @@ -37,7 +37,7 @@
-Rendered diagram +Rendered diagram after this change ```mermaid classDiagram diff --git a/tests/testutils/_primer/pyreverse_cases/multiple_targets/expected_comment.md b/tests/testutils/_primer/pyreverse_cases/multiple_targets/expected_comment.md index 7df4e7efbd..531287d8a8 100644 --- a/tests/testutils/_primer/pyreverse_cases/multiple_targets/expected_comment.md +++ b/tests/testutils/_primer/pyreverse_cases/multiple_targets/expected_comment.md @@ -37,7 +37,7 @@
-Rendered diagram +Rendered diagram after this change ```mermaid classDiagram @@ -137,7 +137,7 @@ classDiagram
-Rendered diagram +Rendered diagram after this change ```mermaid classDiagram diff --git a/tests/testutils/_primer/pyreverse_cases/relationship_added/expected_comment.md b/tests/testutils/_primer/pyreverse_cases/relationship_added/expected_comment.md index 1ec546c133..17db9b09c2 100644 --- a/tests/testutils/_primer/pyreverse_cases/relationship_added/expected_comment.md +++ b/tests/testutils/_primer/pyreverse_cases/relationship_added/expected_comment.md @@ -54,7 +54,7 @@
-Rendered diagram +Rendered diagram after this change ```mermaid classDiagram From 5a82088f5300ce68f26e6b4b3def212d7db88bba Mon Sep 17 00:00:00 2001 From: Julfried Date: Fri, 17 Apr 2026 09:20:14 +0200 Subject: [PATCH 11/12] Add test folder --- tests/.pyreverse_primer_tests/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/.pyreverse_primer_tests/.gitkeep diff --git a/tests/.pyreverse_primer_tests/.gitkeep b/tests/.pyreverse_primer_tests/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 From fd77ceee6b88dccbe4a8d64fcd39bf71badc1083 Mon Sep 17 00:00:00 2001 From: Julfried Date: Sat, 18 Apr 2026 20:34:47 +0200 Subject: [PATCH 12/12] Make ordering deterministic --- pylint/testutils/_primer/pyreverse_primer_compare_command.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pylint/testutils/_primer/pyreverse_primer_compare_command.py b/pylint/testutils/_primer/pyreverse_primer_compare_command.py index 15f5a7a80a..0be1f4dd31 100644 --- a/pylint/testutils/_primer/pyreverse_primer_compare_command.py +++ b/pylint/testutils/_primer/pyreverse_primer_compare_command.py @@ -126,7 +126,8 @@ def _iter_changes( base_data: PyreversePrimerOutput, new_data: PyreversePrimerOutput, ) -> Iterator[tuple[str, PyreverseTargetData, PyreverseTargetData]]: - for target_name, base_target_data in base_data.items(): + for target_name in sorted(base_data): + base_target_data = base_data[target_name] new_target_data = new_data[target_name] if base_target_data["diagram"] == new_target_data["diagram"]: continue