blob: 762186891b1821ebee08e3b9e78bf886690b31e2 [file] [log] [blame]
# Copyright 2021 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Unit tests for dependency_graph.py."""
import pytest
from chromite.lib import dependency_graph
from chromite.lib.parser import package_info
def _build_depgraph() -> dependency_graph.DependencyGraph:
"""Build a DependencyGraph to test against."""
sysroot = "/build/target/"
sdk_root = "/"
virtual = package_info.parse("virtual/target-foo-1.2.3")
dep = package_info.parse("cat/dep-1.0.0-r1")
depdep = package_info.parse("cat/depdep-2.0.1-r5")
bdep = package_info.parse("cat/bdep-3.4")
virtual_node = dependency_graph.PackageNode(
virtual, sysroot, src_paths=["/virtual/foo"]
)
dep_node = dependency_graph.PackageNode(
dep, sysroot, src_paths=["/cat/dep"]
)
depdep_node = dependency_graph.PackageNode(
depdep, sysroot, src_paths=["/cat/depdep", "/other/depdep"]
)
bdep_node = dependency_graph.PackageNode(
bdep, sdk_root, src_paths=["/cat/bdep"]
)
depbdep_node = dependency_graph.PackageNode(
dep, sdk_root, src_paths=["/cat/dep"]
)
virtual_node.add_dependency(dep_node)
virtual_node.add_dependency(bdep_node)
virtual_node.add_dependency(depbdep_node)
dep_node.add_dependency(depdep_node)
depbdep_node.add_dependency(depdep_node)
nodes = (virtual_node, dep_node, depdep_node, bdep_node, depbdep_node)
return dependency_graph.DependencyGraph(nodes, sysroot, [virtual])
def test_dependency_graph_incorrect_sysroot() -> None:
"""Test building a graph with the wrong sysroot fails."""
pkg = package_info.parse("foo/bar-1.0-r2")
sdk_root = "/"
sysroot = "/build/target"
n1 = dependency_graph.PackageNode(pkg, sdk_root)
n2 = dependency_graph.PackageNode(pkg, sysroot)
with pytest.raises(dependency_graph.SysrootValueError):
dependency_graph.DependencyGraph(
[n1, n2], sysroot="/wrong/sysroot", root_packages=[pkg]
)
def test_create_invalid_depgraph_too_many_roots() -> None:
"""Test building a graph with too many roots fails."""
pkg = package_info.parse("foo/bar-1.0-r2")
n1 = dependency_graph.PackageNode(pkg, "/")
n2 = dependency_graph.PackageNode(pkg, "/build/target")
n3 = dependency_graph.PackageNode(pkg, "/build/other")
with pytest.raises(AssertionError):
dependency_graph.DependencyGraph(
[n1, n2, n3], sysroot="/build/target", root_packages=[pkg]
)
def test_create_invalid_depgraph_node_conflict() -> None:
"""Test building a graph with non-equivalent nodes for one package fails."""
pkg = package_info.parse("foo/bar-1.0-r2")
dep = package_info.parse("foo/baz-2.0")
sysroot = "/build/target"
pkg_node = dependency_graph.PackageNode(pkg, sysroot)
pkg_node2 = dependency_graph.PackageNode(pkg, sysroot)
dep_node = dependency_graph.PackageNode(dep, sysroot)
pkg_node.add_dependency(dep_node)
with pytest.raises(dependency_graph.NodeCollisionError):
dependency_graph.DependencyGraph(
[pkg_node, dep_node, pkg_node2],
sysroot=sysroot,
root_packages=[pkg],
)
def test_create_valid_depgraph_duplicate_nodes() -> None:
"""Test deduplication of nodes in depgraph creation."""
pkg = package_info.parse("foo/bar-1.0-r2")
dep = package_info.parse("foo/baz-2.0")
sysroot = "/build/target"
pkg_node = dependency_graph.PackageNode(pkg, sysroot)
pkg_node2 = dependency_graph.PackageNode(pkg, sysroot)
pkg_node3 = dependency_graph.PackageNode(pkg, sysroot)
dep_node = dependency_graph.PackageNode(dep, sysroot)
pkg_node.add_dependency(dep_node)
pkg_node2.add_dependency(dep_node)
pkg_node3.add_dependency(dep_node)
assert pkg_node == pkg_node2 == pkg_node3
assert len(list(dep_node.reverse_dependencies)) == 1
# Add multiple instances of the same node instance, plus multiple equal
# nodes.
packages = (pkg_node, pkg_node, pkg_node2, pkg_node3, dep_node)
graph = dependency_graph.DependencyGraph(
packages, sysroot=sysroot, root_packages=[pkg]
)
# Should only have the nodes for the two packages at the end.
assert len(graph) == 2
def test_dependency_graph_roots() -> None:
"""Test the roots get parsed correctly."""
graph = _build_depgraph()
assert graph.sdk_root == "/"
assert graph.sysroot_path == "/build/target"
def test_dependency_graph_len() -> None:
"""Test the graph size."""
graph = _build_depgraph()
assert len(graph) == 5
def test_dependency_graph_in() -> None:
"""Test __contains__."""
graph = _build_depgraph()
virtual = package_info.parse("virtual/target-foo-1.2.3")
# Full package info instance should be found (via cpvr).
assert virtual in graph
# CPVR string should be found.
assert virtual.cpvr in graph
# Atom string should be found.
assert virtual.atom in graph
# Package not in graph should not be found.
assert "not/in-1.0" not in graph
def test_dependency_graph_iter() -> None:
"""Test __iter__ BFS traversal."""
graph = _build_depgraph()
virtual = package_info.parse("virtual/target-foo-1.2.3")
dep = package_info.parse("cat/dep-1.0.0-r1")
depdep = package_info.parse("cat/depdep-2.0.1-r5")
bdep = package_info.parse("cat/bdep-3.4")
# Expected Order:
# 1. the root package (virtual/target-foo).
# 2-4. cat/dep (sysroot), cat/dep (sdk), cat/bdep in any order.
# 5. depdep.
expected_order = [
[virtual],
[dep, bdep],
[dep, bdep],
[dep, bdep],
[depdep],
]
for i, node in enumerate(graph):
assert node.pkg_info in expected_order[i]
def test_dependency_graph_get_nodes_default_values() -> None:
"""Test get_nodes retrieves the expected results with default args."""
graph = _build_depgraph()
assert len(graph.get_nodes()) == 5
def test_dependency_graph_get_nodes() -> None:
"""Test get_nodes retrieves the expected results."""
graph = _build_depgraph()
virtual = package_info.parse("virtual/target-foo-1.2.3")
virtual_atom = package_info.parse("virtual/target-foo")
# Test fetching by different values.
pi_nodes = graph.get_nodes([virtual])
cpvr_nodes = graph.get_nodes([virtual.cpvr])
atom_nodes = graph.get_nodes([virtual.atom])
pi_atom_nodes = graph.get_nodes([virtual_atom])
assert pi_nodes == cpvr_nodes == atom_nodes == pi_atom_nodes
def test_dependency_graph_get_nodes_by_roots() -> None:
"""Test get_nodes correctly filters by the root types."""
graph = _build_depgraph()
dep = package_info.parse("cat/dep-1.0.0-r1")
# Verify the correct counts.
dep_all = graph.get_nodes([dep])
dep_sysroot = graph.get_nodes([dep], dependency_graph.RootType.SYSROOT)
dep_sdk = graph.get_nodes([dep], dependency_graph.RootType.SDK)
assert len(dep_all) == 2
assert len(dep_sysroot) == 1
assert len(dep_sdk) == 1
# Verify we got the correct nodes.
dep_sysroot_node = dep_sysroot.pop()
dep_sdk_node = dep_sdk.pop()
assert dep_sysroot_node in dep_all
assert dep_sdk_node in dep_all
assert dep_sysroot_node.root == "/build/target"
assert dep_sdk_node.root == "/"
def test_dependency_graph_get_nodes_multi_version_package() -> None:
"""Test handling of multiple packages with different versions."""
virtual = package_info.parse("virtual/foo-1.0")
pkg1 = package_info.parse("cat/pkg-1.0")
pkg2 = package_info.parse("cat/pkg-2.0")
sysroot = "/build/target"
sdk = "/"
virtual_pn = dependency_graph.PackageNode(virtual, sysroot)
pkg1_pn = dependency_graph.PackageNode(pkg1, sysroot)
pkg1_pn_sdk = dependency_graph.PackageNode(pkg1, sdk)
pkg2_pn = dependency_graph.PackageNode(pkg2, sysroot)
pkg2_pn_sdk = dependency_graph.PackageNode(pkg2, sdk)
virtual_pn.add_dependency(pkg1_pn)
virtual_pn.add_dependency(pkg1_pn_sdk)
virtual_pn.add_dependency(pkg2_pn)
virtual_pn.add_dependency(pkg2_pn_sdk)
graph = dependency_graph.DependencyGraph(
(virtual_pn,), sysroot, root_packages=[virtual]
)
# Should get both instances of cat/pkg-1.0.
assert len(graph.get_nodes([pkg1])) == 2
assert len(graph.get_nodes([pkg1.cpvr])) == 2
assert all(p.pkg_info.cpvr == pkg1.cpvr for p in graph.get_nodes([pkg1]))
# Should get both instances of cat/pkg-2.0.
assert len(graph.get_nodes([pkg2])) == 2
assert len(graph.get_nodes([pkg2.cpvr])) == 2
assert all(p.pkg_info.cpvr == pkg2.cpvr for p in graph.get_nodes([pkg2]))
atom = package_info.parse(pkg1.atom)
# Should get both instances of cat/pkg-1.0 and both instances of
# cat/pkg-2.0.
assert len(graph.get_nodes([atom])) == 4
assert len(graph.get_nodes([atom.atom])) == 4
assert all(p.pkg_info.atom == atom.atom for p in graph.get_nodes([atom]))
def test_dependency_graph_dependencies() -> None:
"""Tests for get_dependencies and get_reverse_dependencies."""
graph = _build_depgraph()
virtual = package_info.parse("virtual/target-foo-1.2.3")
dep = package_info.parse("cat/dep-1.0.0-r1")
depdep = package_info.parse("cat/depdep-2.0.1-r5")
bdep = package_info.parse("cat/bdep-3.4")
dep_nodes = graph.get_nodes([dep])
bdep_node = graph.get_nodes(
[bdep], root_type=dependency_graph.RootType.SDK
).pop()
# The virtual has three dependencies; dep x2(sdk + sysroot), and bdep.
assert set(graph.get_dependencies(virtual)) == {*dep_nodes, bdep_node}
# Virtual has no revdeps.
assert not list(graph.get_reverse_dependencies(virtual))
# Leaf node has no dependencies.
assert not list(graph.get_dependencies(depdep))
# Leaf node has two revdeps.
assert set(graph.get_reverse_dependencies(depdep)) == set(dep_nodes)
def test_dependency_graph_is_dependency() -> None:
"""Test is_dependency method."""
graph = _build_depgraph()
virtual = package_info.parse("virtual/target-foo-1.2.3")
dep = package_info.parse("cat/dep-1.0.0-r1")
depdep = package_info.parse("cat/depdep-2.0.1-r5")
bdep = package_info.parse("cat/bdep-3.4")
# Direct dependency.
assert graph.is_dependency(dep, virtual)
# Specifying direct=True should make no difference.
assert graph.is_dependency(dep, virtual, direct=True)
# Indirect dependency.
assert graph.is_dependency(depdep, virtual)
# Indirect dependency checking direct only.
assert not graph.is_dependency(depdep, virtual, direct=True)
# Not a dependency.
assert not graph.is_dependency(depdep, bdep)
def test_dependency_graph_is_dependency_root_types() -> None:
"""Test is_dependency method when using the root type filters."""
graph = _build_depgraph()
virtual = package_info.parse("virtual/target-foo-1.2.3")
bdep = package_info.parse("cat/bdep-3.4")
# Valid dependency when filtering roots.
# bdep is in the SDK.
assert graph.is_dependency(
bdep, virtual, dep_root_type=dependency_graph.RootType.SDK
)
# bdep is also direct dependency.
assert graph.is_dependency(
bdep, virtual, dep_root_type=dependency_graph.RootType.SDK, direct=True
)
# virtual is in the sysroot.
assert graph.is_dependency(
bdep,
virtual,
dep_root_type=dependency_graph.RootType.SDK,
src_root_type=dependency_graph.RootType.SYSROOT,
)
# Invalid dependency when filtering roots.
# bdep is not in the sysroot.
assert not graph.is_dependency(
bdep, virtual, dep_root_type=dependency_graph.RootType.SYSROOT
)
# virtual is not in the SDK.
assert not graph.is_dependency(
bdep, virtual, src_root_type=dependency_graph.RootType.SDK
)
def test_depedency_graph_is_relevant() -> None:
"""Test the is_relevant method."""
graph = _build_depgraph()
# Dependency.
# Fetch node and call directly.
assert graph.get_nodes(["cat/depdep"])[0].is_relevant("/cat/depdep")
# Search the depgraph.
assert graph.is_relevant("/cat/depdep")
# Reverse dependency.
assert graph.is_relevant("/cat/bdep")
# Irrelevant path.
assert not graph.is_relevant("/not/relevant")
def test_dependency_graph_any_relevant() -> None:
"""Test the any_relevant method."""
graph = _build_depgraph()
# Both relevant.
assert graph.any_relevant(["/cat/dep", "/cat/bdep"])
# One relevant.
assert graph.any_relevant(["/not/relevant", "/cat/bdep"])
# Neither relevant.
assert not graph.any_relevant(["/not/relevant", "/also/not/relevant"])
def test_get_relevant_nodes() -> None:
"""Test the get_relevant_nodes method."""
graph = _build_depgraph()
dep = package_info.parse("cat/dep-1.0.0-r1")
# No root type specified, defaults to all.
assert set(graph.get_relevant_nodes(["/cat/dep"])) == set(
graph.get_nodes([dep])
)
# Get dependencies for all root types.
assert set(
graph.get_relevant_nodes(["/cat/dep"], dependency_graph.RootType.ALL)
) == set(graph.get_nodes([dep]))
# Only sysroot dependencies.
assert set(
graph.get_relevant_nodes(
["/cat/dep"], dependency_graph.RootType.SYSROOT
)
) == set(graph.get_nodes([dep], dependency_graph.RootType.SYSROOT))
# Only SDK dependencies.
assert set(
graph.get_relevant_nodes(["/cat/dep"], dependency_graph.RootType.SDK)
) == set(graph.get_nodes([dep], dependency_graph.RootType.SDK))