Files
spack/lib/spack/spack/test/abi_splicing.py
2025-01-17 16:27:16 +01:00

285 lines
11 KiB
Python

# Copyright Spack Project Developers. See COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
""" Test ABI-based splicing of dependencies """
from typing import List
import pytest
import spack.concretize
import spack.config
import spack.deptypes as dt
from spack.installer import PackageInstaller
from spack.solver.asp import SolverError
from spack.spec import Spec
def _make_specs_non_buildable(specs: List[str]):
output_config = {}
for spec in specs:
output_config[spec] = {"buildable": False}
return output_config
@pytest.fixture
def install_specs(
mutable_database,
mock_packages,
mutable_config,
do_not_check_runtimes_on_reuse,
install_mockery,
):
"""Returns a function that concretizes and installs a list of abstract specs"""
mutable_config.set("concretizer:reuse", True)
def _impl(*specs_str):
concrete_specs = [Spec(s).concretized() for s in specs_str]
PackageInstaller([s.package for s in concrete_specs], fake=True, explicit=True).install()
return concrete_specs
return _impl
def _enable_splicing():
spack.config.set("concretizer:splice", {"automatic": True})
@pytest.mark.parametrize("spec_str", ["splice-z", "splice-h@1"])
def test_spec_reuse(spec_str, install_specs, mutable_config):
"""Tests reuse of splice-z, without splicing, as a root and as a dependency of splice-h"""
splice_z = install_specs("splice-z@1.0.0+compat")[0]
mutable_config.set("packages", _make_specs_non_buildable(["splice-z"]))
concrete = spack.concretize.concretize_one(spec_str)
assert concrete["splice-z"].satisfies(splice_z)
@pytest.mark.regression("48578")
def test_splice_installed_hash(install_specs, mutable_config):
"""Tests splicing the dependency of an installed spec, for another installed spec"""
splice_t, splice_h = install_specs(
"splice-t@1 ^splice-h@1.0.0+compat ^splice-z@1.0.0",
"splice-h@1.0.2+compat ^splice-z@1.0.0",
)
packages_config = _make_specs_non_buildable(["splice-t", "splice-h"])
mutable_config.set("packages", packages_config)
goal_spec = "splice-t@1 ^splice-h@1.0.2+compat ^splice-z@1.0.0"
with pytest.raises(SolverError):
spack.concretize.concretize_one(goal_spec)
_enable_splicing()
concrete = spack.concretize.concretize_one(goal_spec)
# splice-t has a dependency that is changing, thus its hash should be different
assert concrete.dag_hash() != splice_t.dag_hash()
assert concrete.build_spec.satisfies(splice_t)
assert not concrete.satisfies(splice_t)
# splice-h is reused, so the hash should stay the same
assert concrete["splice-h"].satisfies(splice_h)
assert concrete["splice-h"].build_spec.satisfies(splice_h)
assert concrete["splice-h"].dag_hash() == splice_h.dag_hash()
def test_splice_build_splice_node(install_specs, mutable_config):
"""Tests splicing the dependency of an installed spec, for a spec that is yet to be built"""
splice_t = install_specs("splice-t@1 ^splice-h@1.0.0+compat ^splice-z@1.0.0+compat")[0]
mutable_config.set("packages", _make_specs_non_buildable(["splice-t"]))
goal_spec = "splice-t@1 ^splice-h@1.0.2+compat ^splice-z@1.0.0+compat"
with pytest.raises(SolverError):
spack.concretize.concretize_one(goal_spec)
_enable_splicing()
concrete = spack.concretize.concretize_one(goal_spec)
# splice-t has a dependency that is changing, thus its hash should be different
assert concrete.dag_hash() != splice_t.dag_hash()
assert concrete.build_spec.satisfies(splice_t)
assert not concrete.satisfies(splice_t)
# splice-h should be different
assert concrete["splice-h"].dag_hash() != splice_t["splice-h"].dag_hash()
assert concrete["splice-h"].build_spec.dag_hash() == concrete["splice-h"].dag_hash()
def test_double_splice(install_specs, mutable_config):
"""Tests splicing two dependencies of an installed spec, for other installed specs"""
splice_t, splice_h, splice_z = install_specs(
"splice-t@1 ^splice-h@1.0.0+compat ^splice-z@1.0.0+compat",
"splice-h@1.0.2+compat ^splice-z@1.0.1+compat",
"splice-z@1.0.2+compat",
)
mutable_config.set("packages", _make_specs_non_buildable(["splice-t", "splice-h", "splice-z"]))
goal_spec = "splice-t@1 ^splice-h@1.0.2+compat ^splice-z@1.0.2+compat"
with pytest.raises(SolverError):
spack.concretize.concretize_one(goal_spec)
_enable_splicing()
concrete = spack.concretize.concretize_one(goal_spec)
# splice-t and splice-h have a dependency that is changing, thus its hash should be different
assert concrete.dag_hash() != splice_t.dag_hash()
assert concrete.build_spec.satisfies(splice_t)
assert not concrete.satisfies(splice_t)
assert concrete["splice-h"].dag_hash() != splice_h.dag_hash()
assert concrete["splice-h"].build_spec.satisfies(splice_h)
assert not concrete["splice-h"].satisfies(splice_h)
# splice-z is reused, so the hash should stay the same
assert concrete["splice-z"].dag_hash() == splice_z.dag_hash()
@pytest.mark.parametrize(
"original_spec,goal_spec",
[
# `virtual-abi-1` can be spliced for `virtual-abi-multi abi=one` and vice-versa
(
"depends-on-virtual-with-abi ^virtual-abi-1",
"depends-on-virtual-with-abi ^virtual-abi-multi abi=one",
),
(
"depends-on-virtual-with-abi ^virtual-abi-multi abi=one",
"depends-on-virtual-with-abi ^virtual-abi-1",
),
# `virtual-abi-2` can be spliced for `virtual-abi-multi abi=two` and vice-versa
(
"depends-on-virtual-with-abi ^virtual-abi-2",
"depends-on-virtual-with-abi ^virtual-abi-multi abi=two",
),
(
"depends-on-virtual-with-abi ^virtual-abi-multi abi=two",
"depends-on-virtual-with-abi ^virtual-abi-2",
),
],
)
def test_virtual_multi_splices_in(original_spec, goal_spec, install_specs, mutable_config):
"""Tests that we can splice a virtual dependency with a different, but compatible, provider."""
original = install_specs(original_spec)[0]
mutable_config.set("packages", _make_specs_non_buildable(["depends-on-virtual-with-abi"]))
with pytest.raises(SolverError):
spack.concretize.concretize_one(goal_spec)
_enable_splicing()
spliced = spack.concretize.concretize_one(goal_spec)
assert spliced.dag_hash() != original.dag_hash()
assert spliced.build_spec.dag_hash() == original.dag_hash()
assert spliced["virtual-with-abi"].name != spliced.build_spec["virtual-with-abi"].name
@pytest.mark.parametrize(
"original_spec,goal_spec",
[
# can_splice("manyvariants@1.0.0", when="@1.0.1", match_variants="*")
(
"depends-on-manyvariants ^manyvariants@1.0.0+a+b c=v1 d=v2",
"depends-on-manyvariants ^manyvariants@1.0.1+a+b c=v1 d=v2",
),
(
"depends-on-manyvariants ^manyvariants@1.0.0~a~b c=v3 d=v3",
"depends-on-manyvariants ^manyvariants@1.0.1~a~b c=v3 d=v3",
),
# can_splice("manyvariants@2.0.0+a~b", when="@2.0.1~a+b", match_variants=["c", "d"])
(
"depends-on-manyvariants@2.0 ^manyvariants@2.0.0+a~b c=v3 d=v2",
"depends-on-manyvariants@2.0 ^manyvariants@2.0.1~a+b c=v3 d=v2",
),
# can_splice("manyvariants@2.0.0 c=v1 d=v1", when="@2.0.1+a+b")
(
"depends-on-manyvariants@2.0 ^manyvariants@2.0.0~a~b c=v1 d=v1",
"depends-on-manyvariants@2.0 ^manyvariants@2.0.1+a+b c=v3 d=v3",
),
],
)
def test_manyvariant_matching_variant_splice(
original_spec, goal_spec, install_specs, mutable_config
):
"""Tests splicing with different kind of matching on variants"""
original = install_specs(original_spec)[0]
mutable_config.set("packages", {"depends-on-manyvariants": {"buildable": False}})
with pytest.raises(SolverError):
spack.concretize.concretize_one(goal_spec)
_enable_splicing()
spliced = spack.concretize.concretize_one(goal_spec)
assert spliced.dag_hash() != original.dag_hash()
assert spliced.build_spec.dag_hash() == original.dag_hash()
# The spliced 'manyvariants' is yet to be built
assert spliced["manyvariants"].dag_hash() != original["manyvariants"].dag_hash()
assert spliced["manyvariants"].build_spec.dag_hash() == spliced["manyvariants"].dag_hash()
def test_external_splice_same_name(install_specs, mutable_config):
"""Tests that externals can be spliced for non-external specs"""
original_splice_h, original_splice_t = install_specs(
"splice-h@1.0.0 ^splice-z@1.0.0+compat",
"splice-t@1.0 ^splice-h@1.0.1 ^splice-z@1.0.1+compat",
)
mutable_config.set("packages", _make_specs_non_buildable(["splice-t", "splice-h"]))
mutable_config.set(
"packages",
{
"splice-z": {
"externals": [{"spec": "splice-z@1.0.2+compat", "prefix": "/usr"}],
"buildable": False,
}
},
)
_enable_splicing()
concrete_splice_h = spack.concretize.concretize_one("splice-h@1.0.0 ^splice-z@1.0.2")
concrete_splice_t = spack.concretize.concretize_one(
"splice-t@1.0 ^splice-h@1.0.1 ^splice-z@1.0.2"
)
assert concrete_splice_h.dag_hash() != original_splice_h.dag_hash()
assert concrete_splice_h.build_spec.dag_hash() == original_splice_h.dag_hash()
assert concrete_splice_h["splice-z"].external
assert concrete_splice_t.dag_hash() != original_splice_t.dag_hash()
assert concrete_splice_t.build_spec.dag_hash() == original_splice_t.dag_hash()
assert concrete_splice_t["splice-z"].external
assert concrete_splice_t["splice-z"].dag_hash() == concrete_splice_h["splice-z"].dag_hash()
def test_spliced_build_deps_only_in_build_spec(install_specs):
"""Tests that build specs are not reported in the spliced spec"""
install_specs("splice-t@1.0 ^splice-h@1.0.1 ^splice-z@1.0.0")
_enable_splicing()
spliced = spack.concretize.concretize_one("splice-t@1.0 ^splice-h@1.0.2 ^splice-z@1.0.0")
build_spec = spliced.build_spec
# Spec has been spliced
assert build_spec.dag_hash() != spliced.dag_hash()
# Build spec has spliced build dependencies
assert build_spec.dependencies("splice-h", dt.BUILD)
assert build_spec.dependencies("splice-z", dt.BUILD)
# Spliced build dependencies are removed
assert len(spliced.dependencies(None, dt.BUILD)) == 0
def test_spliced_transitive_dependency(install_specs, mutable_config):
"""Tests that build specs are not reported, even for spliced transitive dependencies"""
install_specs("splice-depends-on-t@1.0 ^splice-h@1.0.1")
mutable_config.set("packages", _make_specs_non_buildable(["splice-depends-on-t"]))
_enable_splicing()
spliced = spack.concretize.concretize_one("splice-depends-on-t^splice-h@1.0.2")
# Spec has been spliced
assert spliced.build_spec.dag_hash() != spliced.dag_hash()
assert spliced["splice-t"].build_spec.dag_hash() != spliced["splice-t"].dag_hash()
# Spliced build dependencies are removed
assert len(spliced.dependencies(None, dt.BUILD)) == 0
assert len(spliced["splice-t"].dependencies(None, dt.BUILD)) == 0