blob: e8e46df2c1ad45f871fa97ae6f7c06263dc3b447 [file] [log] [blame]
# 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
"""Unittest for the main module."""
from __future__ import annotations
import os
import sys
from collections.abc import Iterator
from typing import Any
from unittest import mock
import pytest
from _pytest.capture import CaptureFixture
from _pytest.fixtures import SubRequest
from pylint.lint import augmented_sys_path, discover_package_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() -> Iterator[mock.MagicMock]:
with mock.patch("pylint.pyreverse.utils.subprocess") as mock_subprocess:
yield mock_subprocess
@pytest.fixture
def mock_graphviz(mock_subprocess: mock.MagicMock) -> Iterator[None]:
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: SubRequest) -> Iterator[None]:
current_sys_path = list(sys.path)
sys.path[:] = []
current_dir = os.getcwd()
os.chdir(request.param)
yield
os.chdir(current_dir)
sys.path[:] = current_sys_path
@pytest.mark.usefixtures("setup_path")
def test_project_root_in_sys_path() -> None:
"""Test the context manager adds the project root directory to sys.path.
This should happen when pyreverse is run from any directory.
"""
with augmented_sys_path([discover_package_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: 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])
# 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..."
in capsys.readouterr().out
)
# 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
@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.MagicMock, mock_subprocess: mock.MagicMock, capsys: CaptureFixture
) -> 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])
# 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 and we exit cleanly
mock_writer.DiagramWriter().write.assert_called_once()
assert wrapped_sysexit.value.code == 0
@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: CaptureFixture) -> 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", "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
@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_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])
assert "parsing" in capsys.readouterr().out
@pytest.mark.parametrize(
("arg", "expected_default"),
[
("mode", "PUB_ONLY"),
("classes", []),
("show_ancestors", None),
("all_ancestors", None),
("show_associated", None),
("all_associated", None),
("show_builtin", 0),
("show_stdlib", 0),
("module_names", None),
("output_format", "dot"),
("colorized", 0),
("max_color_depth", 2),
("ignore_list", ("CVS",)),
("project", ""),
("output_directory", ""),
],
)
@mock.patch("pylint.pyreverse.main.Run.run", new=mock.MagicMock())
@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]
assert getattr(run.config, arg) == expected_default
@mock.patch("pylint.pyreverse.main.writer")
def test_command_line_arguments_yes_no(
mock_writer: mock.MagicMock, # pylint: disable=unused-argument
) -> None:
"""Regression test for the --module-names option.
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
@mock.patch("pylint.pyreverse.main.writer")
@mock.patch("pylint.pyreverse.main.sys.exit", new=mock.MagicMock())
def test_class_command(
mock_writer: mock.MagicMock, # pylint: disable=unused-argument
) -> None:
"""Regression test for the --class option.
Make sure that we append multiple --class arguments to one option destination.
"""
runner = main.Run( # type: ignore[var-annotated]
[
"--class",
"data.clientmodule_test.Ancestor",
"--class",
"data.property_pattern.PropertyPatterns",
TEST_DATA_DIR,
]
)
assert "data.clientmodule_test.Ancestor" in runner.config.classes
assert "data.property_pattern.PropertyPatterns" in runner.config.classes
def test_version_info(
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture
) -> None:
"""Test that it is possible to display the version information."""
test_full_version = "1.2.3.4"
monkeypatch.setattr(main.constants, "full_version", test_full_version) # type: ignore[attr-defined]
with pytest.raises(SystemExit):
main.Run(["--version"])
out, _ = capsys.readouterr()
assert "pyreverse is included in pylint" in out
assert test_full_version in out