From 15a5ac03c45f6888a5a3b6acc72a72da27495567 Mon Sep 17 00:00:00 2001 From: Julfried <51880314+Julfried@users.noreply.github.com> Date: Thu, 31 Oct 2024 20:33:24 +0100 Subject: [PATCH] Add detailed documentation for Pyreverse options and fix `__init__` (#10045) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add comprehensive documentation for Pyreverse command-line options * Add configuration section to Pyreverse documentation * Also make sure that `Run.__init__` doesn't return `NoReturn` Co-authored-by: Daniƫl van Noord <13665637+DanielNoord@users.noreply.github.com> --- .../pyreverse/configuration.rst | 169 ++++++++++++++++++ .../pyreverse/index.rst} | 13 +- .../symilar/index.rst} | 0 doc/conf.py | 1 + doc/exts/pyreverse_configuration.py | 75 ++++++++ doc/index.rst | 4 +- doc/whatsnew/fragments/9689.breaking | 7 + pylint/__init__.py | 2 +- pylint/pyreverse/inspector.py | 4 +- pylint/pyreverse/main.py | 151 +++++++++------- tests/pyreverse/test_main.py | 25 +-- tests/pyreverse/test_pyreverse_functional.py | 15 +- 12 files changed, 371 insertions(+), 95 deletions(-) create mode 100644 doc/additional_tools/pyreverse/configuration.rst rename doc/{pyreverse.rst => additional_tools/pyreverse/index.rst} (89%) rename doc/{symilar.rst => additional_tools/symilar/index.rst} (100%) create mode 100644 doc/exts/pyreverse_configuration.py create mode 100644 doc/whatsnew/fragments/9689.breaking diff --git a/doc/additional_tools/pyreverse/configuration.rst b/doc/additional_tools/pyreverse/configuration.rst new file mode 100644 index 0000000000..aa42e8c9a5 --- /dev/null +++ b/doc/additional_tools/pyreverse/configuration.rst @@ -0,0 +1,169 @@ +.. This file is auto-generated. Make any changes to the associated +.. docs extension in 'doc/exts/pyreverse_configuration.py'. + + + +Pyreverse Configuration +^^^^^^^^^^^^^^^^^^^^^^^ + + +Filtering and Scope +------------------- + +--all-ancestors +""""""""""""""" +*Show all ancestors of all classes in .* + +**Default:** ``None`` + + +--all-associated +"""""""""""""""" +*Show all classes associated with the target classes, including indirect associations.* + +**Default:** ``None`` + + +--class +""""""" +*Create a class diagram with all classes related to ; this uses by default the options -ASmy* + +**Default:** ``None`` + + +--filter-mode +""""""""""""" +*Filter attributes and functions according to . Correct modes are: +'PUB_ONLY' filter all non public attributes [DEFAULT], equivalent to PRIVATE+SPECIAL +'ALL' no filter +'SPECIAL' filter Python special functions except constructor +'OTHER' filter protected and private attributes* + +**Default:** ``PUB_ONLY`` + + +--show-ancestors +"""""""""""""""" +*Show generations of ancestor classes not in .* + +**Default:** ``None`` + + +--show-associated +""""""""""""""""" +*Show levels of associated classes not in .* + +**Default:** ``None`` + + +--show-builtin +"""""""""""""" +*Include builtin objects in representation of classes.* + +**Default:** ``False`` + + +--show-stdlib +""""""""""""" +*Include standard library objects in representation of classes.* + +**Default:** ``False`` + + + + +Display Options +--------------- + +--color-palette +""""""""""""""" +*Comma separated list of colors to use for the package depth coloring.* + +**Default:** ``('#77AADD', '#99DDFF', '#44BB99', '#BBCC33', '#AAAA00', '#EEDD88', '#EE8866', '#FFAABB', '#DDDDDD')`` + + +--colorized +""""""""""" +*Use colored output. Classes/modules of the same package get the same color.* + +**Default:** ``False`` + + +--max-color-depth +""""""""""""""""" +*Use separate colors up to package depth of . Higher depths will reuse colors.* + +**Default:** ``2`` + + +--module-names +"""""""""""""" +*Include module name in the representation of classes.* + +**Default:** ``None`` + + +--no-standalone +""""""""""""""" +*Only show nodes with connections.* + +**Default:** ``False`` + + +--only-classnames +""""""""""""""""" +*Don't show attributes and methods in the class boxes; this disables -f values.* + +**Default:** ``False`` + + + + +Output Control +-------------- + +--output +"""""""" +*Create a *. output file if format is available. Available formats are: .dot, .puml, .plantuml, .mmd, .html. Any other format will be tried to be created by using the 'dot' command line tool, which requires a graphviz installation. In this case, these additional formats are available (see `Graphviz output formats `_).* + +**Default:** ``dot`` + + +--output-directory +"""""""""""""""""" +*Set the output directory path.* + +**Default:** ``""`` + + + + +Project Configuration +--------------------- + +--ignore +"""""""" +*Files or directories to be skipped. They should be base names, not paths.* + +**Default:** ``('CVS',)`` + + +--project +""""""""" +*Set the project name. This will later be appended to the output file names.* + +**Default:** ``""`` + + +--source-roots +"""""""""""""" +*Add paths to the list of the source roots. Supports globbing patterns. The source root is an absolute path or a path relative to the current working directory used to determine a package namespace for modules located under the source root.* + +**Default:** ``()`` + + +--verbose +""""""""" +*Makes pyreverse more verbose/talkative. Mostly useful for debugging.* + +**Default:** ``False`` diff --git a/doc/pyreverse.rst b/doc/additional_tools/pyreverse/index.rst similarity index 89% rename from doc/pyreverse.rst rename to doc/additional_tools/pyreverse/index.rst index 7595683d75..41f74731dc 100644 --- a/doc/pyreverse.rst +++ b/doc/additional_tools/pyreverse/index.rst @@ -22,7 +22,6 @@ To see a full list of the available options, run:: pyreverse -h - Example Output '''''''''''''' @@ -31,7 +30,7 @@ Example diagrams generated with the ``.puml`` output format are shown below. Class Diagram ............. -.. image:: media/pyreverse_example_classes.png +.. image:: ../../media/pyreverse_example_classes.png :width: 625 :height: 589 :alt: Class diagram generated by pyreverse @@ -41,7 +40,7 @@ Class Diagram Package Diagram ............... -.. image:: media/pyreverse_example_packages.png +.. image:: ../../media/pyreverse_example_packages.png :width: 344 :height: 177 :alt: Package diagram generated by pyreverse @@ -60,8 +59,14 @@ For example, running:: will generate the full class and package diagrams for ``pylint``, but will additionally generate a file ``pylint.checkers.classes.ClassChecker.dot``: -.. image:: media/ClassChecker_diagram.png +.. image:: ../../media/ClassChecker_diagram.png :width: 757 :height: 1452 :alt: Package diagram generated by pyreverse :align: center + +.. toctree:: + :maxdepth: 1 + :hidden: + + configuration diff --git a/doc/symilar.rst b/doc/additional_tools/symilar/index.rst similarity index 100% rename from doc/symilar.rst rename to doc/additional_tools/symilar/index.rst diff --git a/doc/conf.py b/doc/conf.py index 50d891eb93..741d1975c1 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -44,6 +44,7 @@ "pylint_extensions", "pylint_messages", "pylint_options", + "pyreverse_configuration", "sphinx.ext.autosectionlabel", "sphinx.ext.intersphinx", "sphinx_reredirects", diff --git a/doc/exts/pyreverse_configuration.py b/doc/exts/pyreverse_configuration.py new file mode 100644 index 0000000000..f4d77ef216 --- /dev/null +++ b/doc/exts/pyreverse_configuration.py @@ -0,0 +1,75 @@ +# 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 + +"""Script used to generate the pyreverse configuration page.""" + +from __future__ import annotations + +from pathlib import Path +from typing import NamedTuple + +from sphinx.application import Sphinx + +from pylint.pyreverse.main import OPTIONS_GROUPS, Run +from pylint.typing import OptionDict +from pylint.utils import get_rst_title + + +class OptionsData(NamedTuple): + name: str + optdict: OptionDict + + +PYREVERSE_PATH = ( + Path(__file__).resolve().parent.parent / "additional_tools" / "pyreverse" +) +"""Path to the pyreverse documentation folder.""" + + +def _write_config_page(run: Run) -> None: + """Create or overwrite the configuration page.""" + sections: list[str] = [ + ".. This file is auto-generated. Make any changes to the associated\n" + ".. docs extension in 'doc/exts/pyreverse_configuration.py'.\n\n", + get_rst_title("Pyreverse Configuration", "^"), + ] + + options: list[OptionsData] = [OptionsData(name, info) for name, info in run.options] + option_groups: dict[str, list[str]] = {g: [] for g in OPTIONS_GROUPS.values()} + + for option in sorted(options, key=lambda x: x.name): + option_string = get_rst_title(f"--{option.name}", '"') + option_string += f"*{option.optdict.get('help')}*\n\n" + + if option.optdict.get("default") == "": + option_string += '**Default:** ``""``\n\n\n' + else: + option_string += f"**Default:** ``{option.optdict.get('default')}``\n\n\n" + + option_groups[str(option.optdict.get("group"))].append(option_string) + + for group_title in OPTIONS_GROUPS.values(): + sections.append( + get_rst_title(group_title, "-") + "\n" + "".join(option_groups[group_title]) + ) + + # Join all sections and remove the final two newlines + final_page = "\n\n".join(sections)[:-2] + + with open(PYREVERSE_PATH / "configuration.rst", "w", encoding="utf-8") as stream: + stream.write(final_page) + + +# pylint: disable-next=unused-argument +def build_options_page(app: Sphinx | None) -> None: + # Write configuration page + _write_config_page(Run([])) + + +def setup(app: Sphinx) -> dict[str, bool]: + """Connects the extension to the Sphinx process.""" + # Register callback at the builder-inited Sphinx event + # See https://www.sphinx-doc.org/en/master/extdev/appapi.html + app.connect("builder-inited", build_options_page) + return {"parallel_read_safe": True} diff --git a/doc/index.rst b/doc/index.rst index 1e2c8f044a..8bcdeac8bb 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -33,8 +33,8 @@ :titlesonly: :hidden: - pyreverse - symilar + additional_tools/pyreverse/index + additional_tools/symilar/index .. toctree:: :caption: Changelog diff --git a/doc/whatsnew/fragments/9689.breaking b/doc/whatsnew/fragments/9689.breaking new file mode 100644 index 0000000000..5b175304a5 --- /dev/null +++ b/doc/whatsnew/fragments/9689.breaking @@ -0,0 +1,7 @@ +`pyreverse` `Run` was changed to no longer call `sys.exit()` in its `__init__`. +You should now call `Run(args).run()` which will return the exit code instead. +Having a class that always raised a `SystemExit` exception was considered a bug. + +Normal usage of pyreverse through the CLI will not be affected by this change. + +Refs #9689 diff --git a/pylint/__init__.py b/pylint/__init__.py index 74bde8a394..d3ddf71f62 100644 --- a/pylint/__init__.py +++ b/pylint/__init__.py @@ -53,7 +53,7 @@ def run_pyreverse(argv: Sequence[str] | None = None) -> NoReturn: """ from pylint.pyreverse.main import Run as PyreverseRun - PyreverseRun(argv or sys.argv[1:]) + sys.exit(PyreverseRun(argv or sys.argv[1:]).run()) def run_symilar(argv: Sequence[str] | None = None) -> NoReturn: diff --git a/pylint/pyreverse/inspector.py b/pylint/pyreverse/inspector.py index 8825363fa7..40fb8d6079 100644 --- a/pylint/pyreverse/inspector.py +++ b/pylint/pyreverse/inspector.py @@ -13,7 +13,7 @@ import os import traceback from abc import ABC, abstractmethod -from collections.abc import Callable +from collections.abc import Callable, Sequence from typing import Optional import astroid @@ -346,7 +346,7 @@ def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None: def project_from_files( - files: list[str], + files: Sequence[str], func_wrapper: _WrapperFuncT = _astroid_wrapper, project_name: str = "no name", black_list: tuple[str, ...] = constants.DEFAULT_IGNORE_LIST, diff --git a/pylint/pyreverse/main.py b/pylint/pyreverse/main.py index 3ba0b6c77d..972a46741f 100644 --- a/pylint/pyreverse/main.py +++ b/pylint/pyreverse/main.py @@ -8,7 +8,6 @@ import sys from collections.abc import Sequence -from typing import NoReturn from pylint import constants from pylint.config.arguments_manager import _ArgumentsManager @@ -46,7 +45,17 @@ "#DDDDDD", # pale grey ) + +OPTIONS_GROUPS = { + "FILTERING": "Filtering and Scope", + "DISPLAY": "Display Options", + "OUTPUT": "Output Control", + "PROJECT": "Project Configuration", +} + + OPTIONS: Options = ( + # Filtering and Scope options ( "filter-mode", { @@ -56,15 +65,12 @@ "type": "string", "action": "store", "metavar": "", - "help": """filter attributes and functions according to - . Correct modes are : - 'PUB_ONLY' filter all non public attributes - [DEFAULT], equivalent to PRIVATE+SPECIAL_A - 'ALL' no filter - 'SPECIAL' filter Python special functions - except constructor - 'OTHER' filter protected and private - attributes""", + "group": OPTIONS_GROUPS["FILTERING"], + "help": """Filter attributes and functions according to . Correct modes are: +'PUB_ONLY' filter all non public attributes [DEFAULT], equivalent to PRIVATE+SPECIAL +'ALL' no filter +'SPECIAL' filter Python special functions except constructor +'OTHER' filter protected and private attributes""", }, ), ( @@ -76,7 +82,8 @@ "type": "csv", "dest": "classes", "default": None, - "help": "create a class diagram with all classes related to ;\ + "group": OPTIONS_GROUPS["FILTERING"], + "help": "Create a class diagram with all classes related to ;\ this uses by default the options -ASmy", }, ), @@ -88,7 +95,8 @@ "metavar": "", "type": "int", "default": None, - "help": "show generations of ancestor classes not in ", + "group": OPTIONS_GROUPS["FILTERING"], + "help": "Show generations of ancestor classes not in .", }, ), ( @@ -97,7 +105,8 @@ "short": "A", "default": None, "action": "store_true", - "help": "show all ancestors off all classes in ", + "group": OPTIONS_GROUPS["FILTERING"], + "help": "Show all ancestors of all classes in .", }, ), ( @@ -108,7 +117,8 @@ "metavar": "", "type": "int", "default": None, - "help": "show levels of associated classes not in ", + "group": OPTIONS_GROUPS["FILTERING"], + "help": "Show levels of associated classes not in .", }, ), ( @@ -117,7 +127,8 @@ "short": "S", "default": None, "action": "store_true", - "help": "show recursively all associated off all associated classes", + "group": OPTIONS_GROUPS["FILTERING"], + "help": "Show all classes associated with the target classes, including indirect associations.", }, ), ( @@ -126,7 +137,8 @@ "short": "b", "action": "store_true", "default": False, - "help": "include builtin objects in representation of classes", + "group": OPTIONS_GROUPS["FILTERING"], + "help": "Include builtin objects in representation of classes.", }, ), ( @@ -135,9 +147,11 @@ "short": "L", "action": "store_true", "default": False, - "help": "include standard library objects in representation of classes", + "group": OPTIONS_GROUPS["FILTERING"], + "help": "Include standard library objects in representation of classes.", }, ), + # Display Options ( "module-names", { @@ -145,7 +159,8 @@ "default": None, "type": "yn", "metavar": "", - "help": "include module name in representation of classes", + "group": OPTIONS_GROUPS["DISPLAY"], + "help": "Include module name in the representation of classes.", }, ), ( @@ -154,7 +169,8 @@ "short": "k", "action": "store_true", "default": False, - "help": "don't show attributes and methods in the class boxes; this disables -f values", + "group": OPTIONS_GROUPS["DISPLAY"], + "help": "Don't show attributes and methods in the class boxes; this disables -f values.", }, ), ( @@ -162,24 +178,8 @@ { "action": "store_true", "default": False, - "help": "only show nodes with connections", - }, - ), - ( - "output", - { - "short": "o", - "dest": "output_format", - "action": "store", - "default": "dot", - "metavar": "", - "type": "string", - "help": ( - "create a *. output file if format is available. Available " - f"formats are: {', '.join(DIRECTLY_SUPPORTED_FORMATS)}. Any other " - f"format will be tried to create by means of the 'dot' command line " - f"tool, which requires a graphviz installation." - ), + "group": OPTIONS_GROUPS["DISPLAY"], + "help": "Only show nodes with connections.", }, ), ( @@ -188,6 +188,7 @@ "dest": "colorized", "action": "store_true", "default": False, + "group": OPTIONS_GROUPS["DISPLAY"], "help": "Use colored output. Classes/modules of the same package get the same color.", }, ), @@ -199,7 +200,8 @@ "default": 2, "metavar": "", "type": "int", - "help": "Use separate colors up to package depth of ", + "group": OPTIONS_GROUPS["DISPLAY"], + "help": "Use separate colors up to package depth of . Higher depths will reuse colors.", }, ), ( @@ -210,9 +212,43 @@ "default": DEFAULT_COLOR_PALETTE, "metavar": "", "type": "csv", - "help": "Comma separated list of colors to use", + "group": OPTIONS_GROUPS["DISPLAY"], + "help": "Comma separated list of colors to use for the package depth coloring.", }, ), + # Output Control options + ( + "output", + { + "short": "o", + "dest": "output_format", + "action": "store", + "default": "dot", + "metavar": "", + "type": "string", + "group": OPTIONS_GROUPS["OUTPUT"], + "help": ( + "Create a *. output file if format is available. Available " + f"formats are: {', '.join('.' + fmt for fmt in DIRECTLY_SUPPORTED_FORMATS)}. Any other " + "format will be tried to be created by using the 'dot' command line " + "tool, which requires a graphviz installation. In this case, these additional " + "formats are available (see `Graphviz output formats `_)." + ), + }, + ), + ( + "output-directory", + { + "default": "", + "type": "path", + "short": "d", + "action": "store", + "metavar": "", + "group": OPTIONS_GROUPS["OUTPUT"], + "help": "Set the output directory path.", + }, + ), + # Project Configuration options ( "ignore", { @@ -220,6 +256,7 @@ "metavar": "", "dest": "ignore_list", "default": constants.DEFAULT_IGNORE_LIST, + "group": OPTIONS_GROUPS["PROJECT"], "help": "Files or directories to be skipped. They should be base names, not paths.", }, ), @@ -230,18 +267,8 @@ "type": "string", "short": "p", "metavar": "", - "help": "set the project name.", - }, - ), - ( - "output-directory", - { - "default": "", - "type": "path", - "short": "d", - "action": "store", - "metavar": "", - "help": "set the output directory path.", + "group": OPTIONS_GROUPS["PROJECT"], + "help": "Set the project name. This will later be appended to the output file names.", }, ), ( @@ -250,6 +277,7 @@ "type": "glob_paths_csv", "metavar": "[,...]", "default": (), + "group": OPTIONS_GROUPS["PROJECT"], "help": "Add paths to the list of the source roots. Supports globbing patterns. The " "source root is an absolute path or a path relative to the current working directory " "used to determine a package namespace for modules located under the source root.", @@ -260,19 +288,19 @@ { "action": "store_true", "default": False, + "group": OPTIONS_GROUPS["PROJECT"], "help": "Makes pyreverse more verbose/talkative. Mostly useful for debugging.", }, ), ) +# Base class providing common behaviour for pyreverse commands class Run(_ArgumentsManager, _ArgumentsProvider): - """Base class providing common behaviour for pyreverse commands.""" - options = OPTIONS name = "pyreverse" - def __init__(self, args: Sequence[str]) -> NoReturn: + def __init__(self, args: Sequence[str]) -> None: # Immediately exit if user asks for version if "--version" in args: print("pyreverse is included in pylint:") @@ -284,7 +312,7 @@ def __init__(self, args: Sequence[str]) -> NoReturn: # Parse options insert_default_options() - args = self._parse_command_line_configuration(args) + self.args = self._parse_command_line_configuration(args) if self.config.output_format not in DIRECTLY_SUPPORTED_FORMATS: check_graphviz_availability() @@ -294,19 +322,17 @@ def __init__(self, args: Sequence[str]) -> NoReturn: ) check_if_graphviz_supports_format(self.config.output_format) - sys.exit(self.run(args)) - - def run(self, args: list[str]) -> int: + def run(self) -> int: """Checking arguments and run project.""" - if not args: + if not self.args: print(self.help()) return 1 extra_packages_paths = list( - {discover_package_path(arg, self.config.source_roots) for arg in args} + {discover_package_path(arg, self.config.source_roots) for arg in self.args} ) with augmented_sys_path(extra_packages_paths): project = project_from_files( - args, + self.args, project_name=self.config.project, black_list=self.config.ignore_list, verbose=self.config.verbose, @@ -319,4 +345,5 @@ def run(self, args: list[str]) -> int: if __name__ == "__main__": - Run(sys.argv[1:]) + arguments = sys.argv[1:] + Run(arguments).run() diff --git a/tests/pyreverse/test_main.py b/tests/pyreverse/test_main.py index 59fcab16f4..37189c5783 100644 --- a/tests/pyreverse/test_main.py +++ b/tests/pyreverse/test_main.py @@ -109,9 +109,7 @@ def test_graphviz_supported_image_format( mock_writer: mock.MagicMock, capsys: CaptureFixture[str] ) -> None: """Test that Graphviz is used if the image format is supported.""" - with pytest.raises(SystemExit) as wrapped_sysexit: - # we have to catch the SystemExit so the test execution does not stop - main.Run(["-o", "png", TEST_DATA_DIR]) + exit_code = main.Run(["-o", "png", TEST_DATA_DIR]).run() # Check that the right info message is shown to the user assert ( "Format png is not supported natively. Pyreverse will try to generate it using Graphviz..." @@ -119,7 +117,7 @@ def test_graphviz_supported_image_format( ) # Check that pyreverse actually made the call to create the diagram and we exit cleanly mock_writer.DiagramWriter().write.assert_called_once() - assert wrapped_sysexit.value.code == 0 + assert exit_code == 0 @mock.patch("pylint.pyreverse.main.Linker", new=mock.MagicMock()) @@ -131,9 +129,7 @@ def test_graphviz_cant_determine_supported_formats( ) -> None: """Test that Graphviz is used if the image format is supported.""" mock_subprocess.run.return_value.stderr = "..." - with pytest.raises(SystemExit) as wrapped_sysexit: - # we have to catch the SystemExit so the test execution does not stop - main.Run(["-o", "png", TEST_DATA_DIR]) + exit_code = main.Run(["-o", "png", TEST_DATA_DIR]).run() # Check that the right info message is shown to the user assert ( "Unable to determine Graphviz supported output formats." @@ -141,7 +137,7 @@ def test_graphviz_cant_determine_supported_formats( ) # Check that pyreverse actually made the call to create the diagram and we exit cleanly mock_writer.DiagramWriter().write.assert_called_once() - assert wrapped_sysexit.value.code == 0 + assert exit_code == 0 @mock.patch("pylint.pyreverse.main.Linker", new=mock.MagicMock()) @@ -170,9 +166,7 @@ def test_graphviz_unsupported_image_format(capsys: CaptureFixture) -> None: @pytest.mark.usefixtures("mock_graphviz") def test_verbose(_: mock.MagicMock, capsys: CaptureFixture[str]) -> None: """Test the --verbose flag.""" - with pytest.raises(SystemExit): - # we have to catch the SystemExit so the test execution does not stop - main.Run(["--verbose", TEST_DATA_DIR]) + main.Run(["--verbose", TEST_DATA_DIR]).run() assert "parsing" in capsys.readouterr().out @@ -200,7 +194,7 @@ def test_verbose(_: mock.MagicMock, capsys: CaptureFixture[str]) -> None: @mock.patch("pylint.pyreverse.main.sys.exit", new=mock.MagicMock()) def test_command_line_arguments_defaults(arg: str, expected_default: Any) -> None: """Test that the default arguments of all options are correct.""" - run = main.Run([TEST_DATA_DIR]) # type: ignore[var-annotated] + run = main.Run([TEST_DATA_DIR]) assert getattr(run.config, arg) == expected_default @@ -213,9 +207,8 @@ def test_command_line_arguments_yes_no( Make sure that we support --module-names=yes syntax instead of using it as a flag. """ - with pytest.raises(SystemExit) as wrapped_sysexit: - main.Run(["--module-names=yes", TEST_DATA_DIR]) - assert wrapped_sysexit.value.code == 0 + exit_code = main.Run(["--module-names=yes", TEST_DATA_DIR]).run() + assert exit_code == 0 @mock.patch("pylint.pyreverse.main.writer") @@ -227,7 +220,7 @@ def test_class_command( Make sure that we append multiple --class arguments to one option destination. """ - runner = main.Run( # type: ignore[var-annotated] + runner = main.Run( [ "--class", "data.clientmodule_test.Ancestor", diff --git a/tests/pyreverse/test_pyreverse_functional.py b/tests/pyreverse/test_pyreverse_functional.py index 715ad3dada..ac8dab1b19 100644 --- a/tests/pyreverse/test_pyreverse_functional.py +++ b/tests/pyreverse/test_pyreverse_functional.py @@ -38,14 +38,13 @@ def test_class_diagrams(testfile: FunctionalPyreverseTestfile, tmp_path: Path) - else: source_roots = "" for output_format in testfile.options["output_formats"]: - with pytest.raises(SystemExit) as sys_exit: - args = ["-o", f"{output_format}", "-d", str(tmp_path)] - if source_roots: - args += ["--source-roots", source_roots] - args.extend(testfile.options["command_line_args"]) - args += [str(input_file)] - Run(args) - assert sys_exit.value.code == 0 + args = ["-o", f"{output_format}", "-d", str(tmp_path)] + if source_roots: + args += ["--source-roots", source_roots] + args.extend(testfile.options["command_line_args"]) + args += [str(input_file)] + exit_code = Run(args).run() + assert exit_code == 0 assert testfile.source.with_suffix(f".{output_format}").read_text( encoding="utf8" ) == (tmp_path / f"classes.{output_format}").read_text(encoding="utf8")