blob: c5271d981d02f30a39d375930e15f3b1b563ae76 [file] [log] [blame]
# Copyright 2021 The Chromium OS Authors. All rights reserved.
# 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():
"""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():
"""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():
"""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():
"""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():
"""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():
"""Test the roots get parsed correctly."""
graph = _build_depgraph()
assert graph.sdk_root == '/'
assert graph.sysroot_path == '/build/target'
def test_dependency_graph_len():
"""Test the graph size."""
graph = _build_depgraph()
assert len(graph) == 5
def test_dependency_graph_in():
"""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():
"""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():
"""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():
"""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():
"""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():
"""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():
"""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():
"""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():
"""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():
"""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'])