-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
pyreverse
: better error messages for unsupported file formats
#5951
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,7 +32,20 @@ | |
from pylint.pyreverse import writer | ||
from pylint.pyreverse.diadefslib import DiadefsHandler | ||
from pylint.pyreverse.inspector import Linker, project_from_files | ||
from pylint.pyreverse.utils import check_graphviz_availability, insert_default_options | ||
from pylint.pyreverse.utils import ( | ||
check_graphviz_availability, | ||
check_if_graphviz_supports_format, | ||
insert_default_options, | ||
) | ||
|
||
DIRECTLY_SUPPORTED_FORMATS = ( | ||
"dot", | ||
"vcg", | ||
"puml", | ||
"plantuml", | ||
"mmd", | ||
"html", | ||
) | ||
|
||
OPTIONS = ( | ||
( | ||
|
@@ -139,7 +152,10 @@ | |
action="store", | ||
default="dot", | ||
metavar="<format>", | ||
help="create a *.<format> output file if format available.", | ||
help=( | ||
f"create a *.<format> output file if format is available. Available formats are: {', '.join(DIRECTLY_SUPPORTED_FORMATS)}. " | ||
f"Any other format will be tried to create by means of the 'dot' command line tool, which requires a graphviz installation." | ||
), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We did not state the directly supported output formats in the help output previously, I think this also makes it a bit clearer what is going on under the hood. |
||
), | ||
), | ||
( | ||
|
@@ -205,15 +221,12 @@ def __init__(self, args: Iterable[str]): | |
super().__init__(usage=__doc__) | ||
insert_default_options() | ||
args = self.load_command_line_configuration(args) | ||
if self.config.output_format not in ( | ||
"dot", | ||
"vcg", | ||
"puml", | ||
"plantuml", | ||
"mmd", | ||
"html", | ||
): | ||
if self.config.output_format not in DIRECTLY_SUPPORTED_FORMATS: | ||
check_graphviz_availability() | ||
print( | ||
f"Format {self.config.output_format} is not supported natively. Pyreverse will try to generate it using Graphviz..." | ||
) | ||
check_if_graphviz_supports_format(self.config.output_format) | ||
|
||
sys.exit(self.run(args)) | ||
|
||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -23,6 +23,7 @@ | |||||
import os | ||||||
import re | ||||||
import shutil | ||||||
import subprocess | ||||||
import sys | ||||||
from typing import Optional, Union | ||||||
|
||||||
|
@@ -292,7 +293,34 @@ def check_graphviz_availability(): | |||||
if shutil.which("dot") is None: | ||||||
print( | ||||||
"The requested output format is currently not available.\n" | ||||||
"Please install 'Graphviz' to have other output formats " | ||||||
"than 'dot' or 'vcg'." | ||||||
"Please install 'Graphviz' to have image output formats." | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The message was outdated as we now support other output formats as well. Reiterating the directly supported output formats here is not necessary I think, so I kept it more general. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
) | ||||||
sys.exit(32) | ||||||
|
||||||
|
||||||
def check_if_graphviz_supports_format(output_format: str) -> None: | ||||||
"""Check if the ``dot`` command supports the requested output format. | ||||||
|
||||||
This is needed if image output is desired and ``dot`` is used to convert | ||||||
from *.gv into the final output format. | ||||||
""" | ||||||
dot_output = subprocess.run( | ||||||
["dot", "-T?"], capture_output=True, check=False, encoding="utf-8" | ||||||
) | ||||||
match = re.match( | ||||||
pattern=r".*Use one of: (?P<formats>(\S*\s?)+)", | ||||||
string=dot_output.stderr.strip(), | ||||||
) | ||||||
if not match: | ||||||
print( | ||||||
"Unable to determine Graphviz supported output formats. " | ||||||
"Pyreverse will continue, but subsequent error messages " | ||||||
"regarding the output format may come from Graphviz directly." | ||||||
) | ||||||
return | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||
supported_formats = match.group("formats") | ||||||
if output_format not in supported_formats.split(): | ||||||
print( | ||||||
f"Format {output_format} is not supported by Graphviz. It supports: {supported_formats}" | ||||||
) | ||||||
sys.exit(32) |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -2,15 +2,39 @@ | |||||||||||||||||||||||
import os | ||||||||||||||||||||||||
import sys | ||||||||||||||||||||||||
from typing import Iterator | ||||||||||||||||||||||||
from unittest import mock | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
import pytest | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
from pylint.lint import fix_import_path | ||||||||||||||||||||||||
from pylint.pyreverse import main | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
TEST_DATA_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "data")) | ||||||||||||||||||||||||
PROJECT_ROOT_DIR = os.path.abspath(os.path.join(TEST_DATA_DIR, "..")) | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
|
||||||||||||||||||||||||
@pytest.fixture(name="mock_subprocess") | ||||||||||||||||||||||||
def mock_utils_subprocess(): | ||||||||||||||||||||||||
with mock.patch("pylint.pyreverse.utils.subprocess") as mock_subprocess: | ||||||||||||||||||||||||
yield mock_subprocess | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
|
||||||||||||||||||||||||
@pytest.fixture | ||||||||||||||||||||||||
def mock_graphviz(mock_subprocess): | ||||||||||||||||||||||||
mock_subprocess.run.return_value = mock.Mock( | ||||||||||||||||||||||||
stderr=( | ||||||||||||||||||||||||
'Format: "XYZ" not recognized. Use one of: ' | ||||||||||||||||||||||||
"bmp canon cgimage cmap cmapx cmapx_np dot dot_json eps exr fig gd " | ||||||||||||||||||||||||
"gd2 gif gv icns ico imap imap_np ismap jp2 jpe jpeg jpg json json0 " | ||||||||||||||||||||||||
"mp pct pdf pic pict plain plain-ext png pov ps ps2 psd sgi svg svgz " | ||||||||||||||||||||||||
"tga tif tiff tk vdx vml vmlz vrml wbmp webp xdot xdot1.2 xdot1.4 xdot_json" | ||||||||||||||||||||||||
) | ||||||||||||||||||||||||
) | ||||||||||||||||||||||||
with mock.patch("pylint.pyreverse.utils.shutil") as mock_shutil: | ||||||||||||||||||||||||
mock_shutil.which.return_value = "/usr/bin/dot" | ||||||||||||||||||||||||
yield | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
|
||||||||||||||||||||||||
@pytest.fixture(params=[PROJECT_ROOT_DIR, TEST_DATA_DIR]) | ||||||||||||||||||||||||
def setup_path(request) -> Iterator: | ||||||||||||||||||||||||
current_sys_path = list(sys.path) | ||||||||||||||||||||||||
|
@@ -29,3 +53,62 @@ def test_project_root_in_sys_path(): | |||||||||||||||||||||||
""" | ||||||||||||||||||||||||
with fix_import_path([TEST_DATA_DIR]): | ||||||||||||||||||||||||
assert sys.path == [PROJECT_ROOT_DIR] | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
|
||||||||||||||||||||||||
@mock.patch("pylint.pyreverse.main.Linker", new=mock.MagicMock()) | ||||||||||||||||||||||||
@mock.patch("pylint.pyreverse.main.DiadefsHandler", new=mock.MagicMock()) | ||||||||||||||||||||||||
@mock.patch("pylint.pyreverse.main.writer") | ||||||||||||||||||||||||
@pytest.mark.usefixtures("mock_graphviz") | ||||||||||||||||||||||||
def test_graphviz_supported_image_format(mock_writer, capsys): | ||||||||||||||||||||||||
"""Test that Graphviz is used if the image format is supported.""" | ||||||||||||||||||||||||
with pytest.raises(SystemExit): | ||||||||||||||||||||||||
# we have to catch the SystemExit so the test execution does not stop | ||||||||||||||||||||||||
main.Run(["-o", "png", TEST_DATA_DIR]) | ||||||||||||||||||||||||
# Check that the right info message is shown to the user | ||||||||||||||||||||||||
assert ( | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I'll let you decide if it's worth it, there's 3 similar assert that could be done if you want to assert that the error code is correct. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I included the check. |
||||||||||||||||||||||||
"Format png is not supported natively. Pyreverse will try to generate it using Graphviz..." | ||||||||||||||||||||||||
in capsys.readouterr().out | ||||||||||||||||||||||||
) | ||||||||||||||||||||||||
# Check that pyreverse actually made the call to create the diagram | ||||||||||||||||||||||||
mock_writer.DiagramWriter().write.assert_called_once() | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
|
||||||||||||||||||||||||
@mock.patch("pylint.pyreverse.main.Linker", new=mock.MagicMock()) | ||||||||||||||||||||||||
@mock.patch("pylint.pyreverse.main.DiadefsHandler", new=mock.MagicMock()) | ||||||||||||||||||||||||
@mock.patch("pylint.pyreverse.main.writer") | ||||||||||||||||||||||||
@pytest.mark.usefixtures("mock_graphviz") | ||||||||||||||||||||||||
def test_graphviz_cant_determine_supported_formats( | ||||||||||||||||||||||||
mock_writer, mock_subprocess, capsys | ||||||||||||||||||||||||
): | ||||||||||||||||||||||||
"""Test that Graphviz is used if the image format is supported.""" | ||||||||||||||||||||||||
mock_subprocess.run.return_value.stderr = "..." | ||||||||||||||||||||||||
with pytest.raises(SystemExit): | ||||||||||||||||||||||||
# we have to catch the SystemExit so the test execution does not stop | ||||||||||||||||||||||||
main.Run(["-o", "png", TEST_DATA_DIR]) | ||||||||||||||||||||||||
# Check that the right info message is shown to the user | ||||||||||||||||||||||||
assert ( | ||||||||||||||||||||||||
"Unable to determine Graphviz supported output formats." | ||||||||||||||||||||||||
in capsys.readouterr().out | ||||||||||||||||||||||||
) | ||||||||||||||||||||||||
# Check that pyreverse actually made the call to create the diagram | ||||||||||||||||||||||||
mock_writer.DiagramWriter().write.assert_called_once() | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
|
||||||||||||||||||||||||
@mock.patch("pylint.pyreverse.main.Linker", new=mock.MagicMock()) | ||||||||||||||||||||||||
@mock.patch("pylint.pyreverse.main.DiadefsHandler", new=mock.MagicMock()) | ||||||||||||||||||||||||
@mock.patch("pylint.pyreverse.main.writer", new=mock.MagicMock()) | ||||||||||||||||||||||||
@pytest.mark.usefixtures("mock_graphviz") | ||||||||||||||||||||||||
def test_graphviz_unsupported_image_format(capsys): | ||||||||||||||||||||||||
"""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", "somethingElse", TEST_DATA_DIR]) | ||||||||||||||||||||||||
# Check that the right info messages are shown to the user | ||||||||||||||||||||||||
stdout = capsys.readouterr().out | ||||||||||||||||||||||||
assert ( | ||||||||||||||||||||||||
"Format somethingElse is not supported natively. Pyreverse will try to generate it using Graphviz..." | ||||||||||||||||||||||||
in stdout | ||||||||||||||||||||||||
) | ||||||||||||||||||||||||
assert "Format somethingElse is not supported by Graphviz. It supports:" in stdout | ||||||||||||||||||||||||
# Check that we exited with the expected error code | ||||||||||||||||||||||||
assert wrapped_sysexit.value.code == 32 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already check that in the
Run
class. No need to do it twice.