139 lines
4.7 KiB
Python
139 lines
4.7 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, Callable, Iterator
|
|
|
|
import pytest
|
|
|
|
from pipdeptree._models import PackageDAG
|
|
from pipdeptree._validate import conflicting_deps, cyclic_deps, render_conflicts_text, render_cycles_text
|
|
|
|
if TYPE_CHECKING:
|
|
from unittest.mock import Mock
|
|
|
|
from tests.our_types import MockGraph
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("mpkgs", "expected_keys", "expected_output"),
|
|
[
|
|
pytest.param(
|
|
{
|
|
("a", "1.0.1"): [("b", [(">=", "2.0.0")])],
|
|
("b", "2.3.0"): [("a", [(">=", "1.0.1")])],
|
|
("c", "4.5.0"): [("d", [("==", "2.0")])],
|
|
("d", "2.0"): [],
|
|
},
|
|
[["a", "b", "a"], ["b", "a", "b"]],
|
|
["Warning!! Cyclic dependencies found:", "* b => a => b", "* a => b => a"],
|
|
id="depth-of-2",
|
|
),
|
|
pytest.param(
|
|
{
|
|
("a", "1.0.1"): [("b", [(">=", "2.0.0")]), ("e", [("==", "2.0")])],
|
|
("b", "2.3.0"): [("c", [(">=", "4.5.0")])],
|
|
("c", "4.5.0"): [("d", [("==", "1.0.1")])],
|
|
("d", "1.0.0"): [("a", [("==", "1.0.1")]), ("e", [("==", "2.0")])],
|
|
("e", "2.0"): [],
|
|
},
|
|
[
|
|
["b", "c", "d", "a", "b"],
|
|
["c", "d", "a", "b", "c"],
|
|
["d", "a", "b", "c", "d"],
|
|
["a", "b", "c", "d", "a"],
|
|
],
|
|
[
|
|
"Warning!! Cyclic dependencies found:",
|
|
"* b => c => d => a => b",
|
|
"* c => d => a => b => c",
|
|
"* d => a => b => c => d",
|
|
"* a => b => c => d => a",
|
|
],
|
|
id="depth-greater-than-2",
|
|
),
|
|
pytest.param(
|
|
{("a", "1.0.1"): [("b", [(">=", "2.0.0")])], ("b", "2.0.0"): []},
|
|
[],
|
|
[],
|
|
id="no-cycle",
|
|
),
|
|
pytest.param(
|
|
{
|
|
("a", "1.0.1"): [("b", [(">=", "2.0.0")])],
|
|
},
|
|
[],
|
|
[],
|
|
id="dependency-not-installed",
|
|
),
|
|
pytest.param({("a", "1.0.1"): []}, [], [], id="no-dependencies"),
|
|
],
|
|
)
|
|
def test_cyclic_deps(
|
|
capsys: pytest.CaptureFixture[str],
|
|
mpkgs: dict[tuple[str, str], list[tuple[str, list[tuple[str, str]]]]],
|
|
expected_keys: list[list[str]],
|
|
expected_output: list[str],
|
|
mock_pkgs: Callable[[MockGraph], Iterator[Mock]],
|
|
) -> None:
|
|
tree = PackageDAG.from_pkgs(list(mock_pkgs(mpkgs)))
|
|
result = cyclic_deps(tree)
|
|
result_keys = [[dep.key for dep in deps] for deps in result]
|
|
assert sorted(expected_keys) == sorted(result_keys)
|
|
render_cycles_text(result)
|
|
captured = capsys.readouterr()
|
|
assert "\n".join(expected_output).strip() == captured.err.strip()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("mpkgs", "expected_keys", "expected_output"),
|
|
[
|
|
(
|
|
{("a", "1.0.1"): [("b", [(">=", "2.3.0")])], ("b", "1.9.1"): []},
|
|
{"a": ["b"]},
|
|
[
|
|
"Warning!!! Possibly conflicting dependencies found:",
|
|
"* a==1.0.1",
|
|
" - b [required: >=2.3.0, installed: 1.9.1]",
|
|
],
|
|
),
|
|
(
|
|
{("a", "1.0.1"): [("c", [(">=", "9.4.1")])], ("b", "2.3.0"): [("c", [(">=", "7.0")])], ("c", "8.0.1"): []},
|
|
{"a": ["c"]},
|
|
[
|
|
"Warning!!! Possibly conflicting dependencies found:",
|
|
"* a==1.0.1",
|
|
" - c [required: >=9.4.1, installed: 8.0.1]",
|
|
],
|
|
),
|
|
(
|
|
{("a", "1.0.1"): [("c", [(">=", "9.4.1")])], ("b", "2.3.0"): [("c", [(">=", "9.4.0")])]},
|
|
{"a": ["c"], "b": ["c"]},
|
|
[
|
|
"Warning!!! Possibly conflicting dependencies found:",
|
|
"* a==1.0.1",
|
|
" - c [required: >=9.4.1, installed: ?]",
|
|
"* b==2.3.0",
|
|
" - c [required: >=9.4.0, installed: ?]",
|
|
],
|
|
),
|
|
(
|
|
{("a", "1.0.1"): [("c", [(">=", "9.4.1")])], ("b", "2.3.0"): [("c", [(">=", "7.0")])], ("c", "9.4.1"): []},
|
|
{},
|
|
[],
|
|
),
|
|
],
|
|
)
|
|
def test_conflicting_deps(
|
|
capsys: pytest.CaptureFixture[str],
|
|
mpkgs: dict[tuple[str, str], list[tuple[str, list[tuple[str, str]]]]],
|
|
expected_keys: dict[str, list[str]],
|
|
expected_output: list[str],
|
|
mock_pkgs: Callable[[MockGraph], Iterator[Mock]],
|
|
) -> None:
|
|
tree = PackageDAG.from_pkgs(list(mock_pkgs(mpkgs)))
|
|
result = conflicting_deps(tree)
|
|
result_keys = {k.key: [v.key for v in vs] for k, vs in result.items()}
|
|
assert expected_keys == result_keys
|
|
render_conflicts_text(result)
|
|
captured = capsys.readouterr()
|
|
assert "\n".join(expected_output).strip() == captured.err.strip()
|