# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import filecmp
import os
import sys
import pytest
import spack.rewiring
import spack.store
from spack.spec import Spec
from spack.test.relocate import text_in_bin
args = ["file"]
if sys.platform == "darwin":
args.extend(["/usr/bin/clang++", "install_name_tool"])
else:
args.extend(["g++", "patchelf"])
@pytest.mark.requires_executables(*args)
@pytest.mark.parametrize("transitive", [True, False])
def test_rewire_db(mock_fetch, install_mockery, transitive):
"""Tests basic rewiring without binary executables."""
spec = Spec("splice-t^splice-h~foo").concretized()
dep = Spec("splice-h+foo").concretized()
spec.package.do_install()
dep.package.do_install()
spliced_spec = spec.splice(dep, transitive=transitive)
assert spec.dag_hash() != spliced_spec.dag_hash()
spack.rewiring.rewire(spliced_spec)
# check that the prefix exists
assert os.path.exists(spliced_spec.prefix)
# test that it made it into the database
rec = spack.store.STORE.db.get_record(spliced_spec)
installed_in_db = rec.installed if rec else False
assert installed_in_db
# check the file in the prefix has the correct paths
for node in spliced_spec.traverse(root=True):
text_file_path = os.path.join(node.prefix, node.name)
with open(text_file_path, "r") as f:
text = f.read()
for modded_spec in node.traverse(root=True):
assert modded_spec.prefix in text
@pytest.mark.requires_executables(*args)
@pytest.mark.parametrize("transitive", [True, False])
def test_rewire_bin(mock_fetch, install_mockery, transitive):
"""Tests basic rewiring with binary executables."""
spec = Spec("quux").concretized()
dep = Spec("garply cflags=-g").concretized()
spec.package.do_install()
dep.package.do_install()
spliced_spec = spec.splice(dep, transitive=transitive)
assert spec.dag_hash() != spliced_spec.dag_hash()
spack.rewiring.rewire(spliced_spec)
# check that the prefix exists
assert os.path.exists(spliced_spec.prefix)
# test that it made it into the database
rec = spack.store.STORE.db.get_record(spliced_spec)
installed_in_db = rec.installed if rec else False
assert installed_in_db
# check the file in the prefix has the correct paths
bin_names = {"garply": "garplinator", "corge": "corgegator", "quux": "quuxifier"}
for node in spliced_spec.traverse(root=True):
for dep in node.traverse(root=True):
bin_file_path = os.path.join(dep.prefix.bin, bin_names[dep.name])
assert text_in_bin(dep.prefix, bin_file_path)
@pytest.mark.requires_executables(*args)
def test_rewire_writes_new_metadata(mock_fetch, install_mockery):
"""Tests that new metadata was written during a rewire.
Accuracy of metadata is left to other tests."""
spec = Spec("quux").concretized()
dep = Spec("garply cflags=-g").concretized()
spec.package.do_install()
dep.package.do_install()
spliced_spec = spec.splice(dep, transitive=True)
spack.rewiring.rewire(spliced_spec)
# test install manifests
for node in spliced_spec.traverse(root=True):
spack.store.STORE.layout.ensure_installed(node)
manifest_file_path = os.path.join(
node.prefix,
spack.store.STORE.layout.metadata_dir,
spack.store.STORE.layout.manifest_file_name,
)
assert os.path.exists(manifest_file_path)
orig_node = spec[node.name]
orig_manifest_file_path = os.path.join(
orig_node.prefix,
spack.store.STORE.layout.metadata_dir,
spack.store.STORE.layout.manifest_file_name,
)
assert os.path.exists(orig_manifest_file_path)
assert not filecmp.cmp(orig_manifest_file_path, manifest_file_path, shallow=False)
specfile_path = os.path.join(
node.prefix,
spack.store.STORE.layout.metadata_dir,
spack.store.STORE.layout.spec_file_name,
)
assert os.path.exists(specfile_path)
orig_specfile_path = os.path.join(
orig_node.prefix,
spack.store.STORE.layout.metadata_dir,
spack.store.STORE.layout.spec_file_name,
)
assert os.path.exists(orig_specfile_path)
assert not filecmp.cmp(orig_specfile_path, specfile_path, shallow=False)
@pytest.mark.requires_executables(*args)
@pytest.mark.parametrize("transitive", [True, False])
def test_uninstall_rewired_spec(mock_fetch, install_mockery, transitive):
"""Test that rewired packages can be uninstalled as normal."""
spec = Spec("quux").concretized()
dep = Spec("garply cflags=-g").concretized()
spec.package.do_install()
dep.package.do_install()
spliced_spec = spec.splice(dep, transitive=transitive)
spack.rewiring.rewire(spliced_spec)
spliced_spec.package.do_uninstall()
assert len(spack.store.STORE.db.query(spliced_spec)) == 0
assert not os.path.exists(spliced_spec.prefix)
@pytest.mark.requires_executables(*args)
def test_rewire_not_installed_fails(mock_fetch, install_mockery):
"""Tests error when an attempt is made to rewire a package that was not
previously installed."""
spec = Spec("quux").concretized()
dep = Spec("garply cflags=-g").concretized()
spliced_spec = spec.splice(dep, False)
with pytest.raises(
spack.rewiring.PackageNotInstalledError,
match="failed due to missing install of build spec",
):
spack.rewiring.rewire(spliced_spec)