1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
|
# 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]
def test_discover_package_path_source_root_as_parent(tmp_path: Any) -> None:
"""Test discover_package_path when source root is a parent of the module."""
# Create this temporary structure:
# /tmp_path/
# └── project/
# └── my-package/
# └── __init__.py
project_dir = tmp_path / "project"
package_dir = project_dir / "mypackage"
package_dir.mkdir(parents=True)
(package_dir / "__init__.py").touch()
# Test with project_dir as source root (parent of package)
result = discover_package_path(str(package_dir), [str(project_dir)])
assert result == str(project_dir)
def test_discover_package_path_source_root_as_child(tmp_path: Any) -> None:
"""Test discover_package_path when source root is a child of the module."""
# Create this temporary structure:
# /tmp_path/
# └── project/
# └── src/
# └── my-package/
# └── __init__.py
project_dir = tmp_path / "project"
src_dir = project_dir / "src"
package_dir = src_dir / "mypackage"
package_dir.mkdir(parents=True)
(package_dir / "__init__.py").touch()
# Test with src_dir as source root (child of project)
result = discover_package_path(str(project_dir), [str(src_dir)])
assert result == str(src_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
|