4333 lines
126 KiB
Python
4333 lines
126 KiB
Python
# 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 glob
|
|
import io
|
|
import os
|
|
import pathlib
|
|
import shutil
|
|
from argparse import Namespace
|
|
|
|
import pytest
|
|
|
|
import llnl.util.filesystem as fs
|
|
import llnl.util.link_tree
|
|
import llnl.util.tty as tty
|
|
from llnl.util.symlink import readlink
|
|
|
|
import spack.cmd.env
|
|
import spack.config
|
|
import spack.environment as ev
|
|
import spack.environment.depfile as depfile
|
|
import spack.environment.environment
|
|
import spack.environment.shell
|
|
import spack.error
|
|
import spack.modules
|
|
import spack.package_base
|
|
import spack.paths
|
|
import spack.repo
|
|
import spack.store
|
|
import spack.util.spack_json as sjson
|
|
import spack.util.spack_yaml
|
|
from spack.cmd.env import _env_create
|
|
from spack.main import SpackCommand, SpackCommandError
|
|
from spack.spec import Spec
|
|
from spack.stage import stage_prefix
|
|
from spack.util.executable import Executable
|
|
from spack.util.path import substitute_path_variables
|
|
from spack.version import Version
|
|
|
|
# TODO-27021
|
|
# everything here uses the mock_env_path
|
|
pytestmark = [
|
|
pytest.mark.usefixtures("mutable_config", "mutable_mock_env_path", "mutable_mock_repo"),
|
|
pytest.mark.maybeslow,
|
|
pytest.mark.not_on_windows("Envs unsupported on Window"),
|
|
]
|
|
|
|
env = SpackCommand("env")
|
|
install = SpackCommand("install")
|
|
add = SpackCommand("add")
|
|
change = SpackCommand("change")
|
|
config = SpackCommand("config")
|
|
remove = SpackCommand("remove")
|
|
concretize = SpackCommand("concretize")
|
|
stage = SpackCommand("stage")
|
|
uninstall = SpackCommand("uninstall")
|
|
find = SpackCommand("find")
|
|
develop = SpackCommand("develop")
|
|
module = SpackCommand("module")
|
|
|
|
sep = os.sep
|
|
|
|
|
|
def setup_combined_multiple_env():
|
|
env("create", "test1")
|
|
test1 = ev.read("test1")
|
|
with test1:
|
|
add("zlib")
|
|
test1.concretize()
|
|
test1.write()
|
|
|
|
env("create", "test2")
|
|
test2 = ev.read("test2")
|
|
with test2:
|
|
add("libelf")
|
|
test2.concretize()
|
|
test2.write()
|
|
|
|
env("create", "--include-concrete", "test1", "--include-concrete", "test2", "combined_env")
|
|
combined = ev.read("combined_env")
|
|
|
|
return test1, test2, combined
|
|
|
|
|
|
@pytest.fixture()
|
|
def environment_from_manifest(tmp_path):
|
|
"""Returns a new environment named 'test' from the content of a manifest file."""
|
|
|
|
def _create(content):
|
|
spack_yaml = tmp_path / ev.manifest_name
|
|
spack_yaml.write_text(content)
|
|
return _env_create("test", init_file=str(spack_yaml))
|
|
|
|
return _create
|
|
|
|
|
|
def check_mpileaks_and_deps_in_view(viewdir):
|
|
"""Check that the expected install directories exist."""
|
|
assert os.path.exists(str(viewdir.join(".spack", "mpileaks")))
|
|
assert os.path.exists(str(viewdir.join(".spack", "libdwarf")))
|
|
|
|
|
|
def check_viewdir_removal(viewdir):
|
|
"""Check that the uninstall/removal worked."""
|
|
assert not os.path.exists(str(viewdir.join(".spack"))) or os.listdir(
|
|
str(viewdir.join(".spack"))
|
|
) == ["projections.yaml"]
|
|
|
|
|
|
def test_add():
|
|
e = ev.create("test")
|
|
e.add("mpileaks")
|
|
assert Spec("mpileaks") in e.user_specs
|
|
|
|
|
|
def test_change_match_spec():
|
|
env("create", "test")
|
|
|
|
e = ev.read("test")
|
|
with e:
|
|
add("mpileaks@2.1")
|
|
add("mpileaks@2.2")
|
|
|
|
change("--match-spec", "mpileaks@2.2", "mpileaks@2.3")
|
|
|
|
assert not any(x.intersects("mpileaks@2.2") for x in e.user_specs)
|
|
assert any(x.intersects("mpileaks@2.3") for x in e.user_specs)
|
|
|
|
|
|
def test_change_multiple_matches():
|
|
env("create", "test")
|
|
|
|
e = ev.read("test")
|
|
with e:
|
|
add("mpileaks@2.1")
|
|
add("mpileaks@2.2")
|
|
add("libelf@0.8.12%clang")
|
|
|
|
change("--match-spec", "mpileaks", "-a", "mpileaks%gcc")
|
|
|
|
assert all(x.intersects("%gcc") for x in e.user_specs if x.name == "mpileaks")
|
|
assert any(x.intersects("%clang") for x in e.user_specs if x.name == "libelf")
|
|
|
|
|
|
def test_env_add_virtual():
|
|
env("create", "test")
|
|
|
|
e = ev.read("test")
|
|
e.add("mpi")
|
|
e.concretize()
|
|
|
|
hashes = e.concretized_order
|
|
assert len(hashes) == 1
|
|
spec = e.specs_by_hash[hashes[0]]
|
|
assert spec.intersects("mpi")
|
|
|
|
|
|
def test_env_add_nonexistant_fails():
|
|
env("create", "test")
|
|
|
|
e = ev.read("test")
|
|
with pytest.raises(ev.SpackEnvironmentError, match=r"no such package"):
|
|
e.add("thispackagedoesnotexist")
|
|
|
|
|
|
def test_env_list(mutable_mock_env_path):
|
|
env("create", "foo")
|
|
env("create", "bar")
|
|
env("create", "baz")
|
|
|
|
out = env("list")
|
|
|
|
assert "foo" in out
|
|
assert "bar" in out
|
|
assert "baz" in out
|
|
|
|
# make sure `spack env list` skips invalid things in var/spack/env
|
|
(mutable_mock_env_path / ".DS_Store").touch()
|
|
out = env("list")
|
|
|
|
assert "foo" in out
|
|
assert "bar" in out
|
|
assert "baz" in out
|
|
assert ".DS_Store" not in out
|
|
|
|
|
|
def test_env_remove(capfd):
|
|
env("create", "foo")
|
|
env("create", "bar")
|
|
|
|
out = env("list")
|
|
assert "foo" in out
|
|
assert "bar" in out
|
|
|
|
foo = ev.read("foo")
|
|
with foo:
|
|
with pytest.raises(SpackCommandError):
|
|
with capfd.disabled():
|
|
env("remove", "-y", "foo")
|
|
assert "foo" in env("list")
|
|
|
|
env("remove", "-y", "foo")
|
|
out = env("list")
|
|
assert "foo" not in out
|
|
assert "bar" in out
|
|
|
|
env("remove", "-y", "bar")
|
|
out = env("list")
|
|
assert "foo" not in out
|
|
assert "bar" not in out
|
|
|
|
|
|
def test_env_rename_managed(capfd):
|
|
# Need real environment
|
|
with pytest.raises(spack.main.SpackCommandError):
|
|
env("rename", "foo", "bar")
|
|
assert (
|
|
"The specified name does not correspond to a managed spack environment"
|
|
in capfd.readouterr()[0]
|
|
)
|
|
|
|
env("create", "foo")
|
|
|
|
out = env("list")
|
|
assert "foo" in out
|
|
|
|
out = env("rename", "foo", "bar")
|
|
assert "Successfully renamed environment foo to bar" in out
|
|
|
|
out = env("list")
|
|
assert "foo" not in out
|
|
assert "bar" in out
|
|
|
|
bar = ev.read("bar")
|
|
with bar:
|
|
# Cannot rename active environment
|
|
with pytest.raises(spack.main.SpackCommandError):
|
|
env("rename", "bar", "baz")
|
|
assert "Cannot rename active environment" in capfd.readouterr()[0]
|
|
|
|
env("create", "qux")
|
|
|
|
# Cannot rename to an active environment (even with force flag)
|
|
with pytest.raises(spack.main.SpackCommandError):
|
|
env("rename", "-f", "qux", "bar")
|
|
assert "bar is an active environment" in capfd.readouterr()[0]
|
|
|
|
# Can rename inactive environment when another's active
|
|
out = env("rename", "qux", "quux")
|
|
assert "Successfully renamed environment qux to quux" in out
|
|
|
|
out = env("list")
|
|
assert "bar" in out
|
|
assert "baz" not in out
|
|
|
|
env("create", "baz")
|
|
|
|
# Cannot rename to existing environment without --force
|
|
with pytest.raises(spack.main.SpackCommandError):
|
|
env("rename", "bar", "baz")
|
|
errmsg = (
|
|
"The new name corresponds to an existing environment;"
|
|
" specify the --force flag to overwrite it."
|
|
)
|
|
assert errmsg in capfd.readouterr()[0]
|
|
|
|
env("rename", "-f", "bar", "baz")
|
|
out = env("list")
|
|
assert "bar" not in out
|
|
assert "baz" in out
|
|
|
|
|
|
def test_env_rename_anonymous(capfd, tmpdir):
|
|
# Need real environment
|
|
with pytest.raises(spack.main.SpackCommandError):
|
|
env("rename", "-d", "./non-existing", "./also-non-existing")
|
|
assert (
|
|
"The specified path does not correspond to a valid spack environment"
|
|
in capfd.readouterr()[0]
|
|
)
|
|
|
|
anon_foo = str(tmpdir / "foo")
|
|
env("create", "-d", anon_foo)
|
|
|
|
anon_bar = str(tmpdir / "bar")
|
|
out = env("rename", "-d", anon_foo, anon_bar)
|
|
assert f"Successfully renamed environment {anon_foo} to {anon_bar}" in out
|
|
assert not ev.is_env_dir(anon_foo)
|
|
assert ev.is_env_dir(anon_bar)
|
|
|
|
# Cannot rename active environment
|
|
anon_baz = str(tmpdir / "baz")
|
|
env("activate", "--sh", "-d", anon_bar)
|
|
with pytest.raises(spack.main.SpackCommandError):
|
|
env("rename", "-d", anon_bar, anon_baz)
|
|
assert "Cannot rename active environment" in capfd.readouterr()[0]
|
|
env("deactivate", "--sh")
|
|
|
|
assert ev.is_env_dir(anon_bar)
|
|
assert not ev.is_env_dir(anon_baz)
|
|
|
|
# Cannot rename to existing environment without --force
|
|
env("create", "-d", anon_baz)
|
|
with pytest.raises(spack.main.SpackCommandError):
|
|
env("rename", "-d", anon_bar, anon_baz)
|
|
errmsg = (
|
|
"The new path corresponds to an existing environment;"
|
|
" specify the --force flag to overwrite it."
|
|
)
|
|
assert errmsg in capfd.readouterr()[0]
|
|
assert ev.is_env_dir(anon_bar)
|
|
assert ev.is_env_dir(anon_baz)
|
|
|
|
env("rename", "-f", "-d", anon_bar, anon_baz)
|
|
assert not ev.is_env_dir(anon_bar)
|
|
assert ev.is_env_dir(anon_baz)
|
|
|
|
# Cannot rename to existing (non-environment) path without --force
|
|
qux = tmpdir / "qux"
|
|
qux.mkdir()
|
|
anon_qux = str(qux)
|
|
assert not ev.is_env_dir(anon_qux)
|
|
|
|
with pytest.raises(spack.main.SpackCommandError):
|
|
env("rename", "-d", anon_baz, anon_qux)
|
|
errmsg = "The new path already exists; specify the --force flag to overwrite it."
|
|
assert errmsg in capfd.readouterr()[0]
|
|
|
|
env("rename", "-f", "-d", anon_baz, anon_qux)
|
|
assert not ev.is_env_dir(anon_baz)
|
|
assert ev.is_env_dir(anon_qux)
|
|
|
|
|
|
def test_concretize():
|
|
e = ev.create("test")
|
|
e.add("mpileaks")
|
|
e.concretize()
|
|
env_specs = e._get_environment_specs()
|
|
assert any(x.name == "mpileaks" for x in env_specs)
|
|
|
|
|
|
def test_env_specs_partition(install_mockery, mock_fetch):
|
|
e = ev.create("test")
|
|
e.add("cmake-client")
|
|
e.concretize()
|
|
|
|
# Single not installed root spec.
|
|
roots_already_installed, roots_to_install = e._partition_roots_by_install_status()
|
|
assert len(roots_already_installed) == 0
|
|
assert len(roots_to_install) == 1
|
|
assert roots_to_install[0].name == "cmake-client"
|
|
|
|
# Single installed root.
|
|
e.install_all()
|
|
roots_already_installed, roots_to_install = e._partition_roots_by_install_status()
|
|
assert len(roots_already_installed) == 1
|
|
assert roots_already_installed[0].name == "cmake-client"
|
|
assert len(roots_to_install) == 0
|
|
|
|
# One installed root, one not installed root.
|
|
e.add("mpileaks")
|
|
e.concretize()
|
|
roots_already_installed, roots_to_install = e._partition_roots_by_install_status()
|
|
assert len(roots_already_installed) == 1
|
|
assert len(roots_to_install) == 1
|
|
assert roots_already_installed[0].name == "cmake-client"
|
|
assert roots_to_install[0].name == "mpileaks"
|
|
|
|
|
|
def test_env_install_all(install_mockery, mock_fetch):
|
|
e = ev.create("test")
|
|
e.add("cmake-client")
|
|
e.concretize()
|
|
e.install_all()
|
|
env_specs = e._get_environment_specs()
|
|
spec = next(x for x in env_specs if x.name == "cmake-client")
|
|
assert spec.installed
|
|
|
|
|
|
def test_env_install_single_spec(install_mockery, mock_fetch):
|
|
env("create", "test")
|
|
install = SpackCommand("install")
|
|
|
|
e = ev.read("test")
|
|
with e:
|
|
install("--add", "cmake-client")
|
|
|
|
e = ev.read("test")
|
|
assert e.user_specs[0].name == "cmake-client"
|
|
assert e.concretized_user_specs[0].name == "cmake-client"
|
|
assert e.specs_by_hash[e.concretized_order[0]].name == "cmake-client"
|
|
|
|
|
|
@pytest.mark.parametrize("unify", [True, False, "when_possible"])
|
|
def test_env_install_include_concrete_env(unify, install_mockery, mock_fetch):
|
|
test1, test2, combined = setup_combined_multiple_env()
|
|
|
|
combined.concretize()
|
|
combined.write()
|
|
|
|
combined.unify = unify
|
|
|
|
with combined:
|
|
install()
|
|
|
|
test1_roots = test1.concretized_order
|
|
test2_roots = test2.concretized_order
|
|
combined_included_roots = combined.included_concretized_order
|
|
|
|
for spec in combined.all_specs():
|
|
assert spec.installed
|
|
|
|
assert test1_roots == combined_included_roots[test1.path]
|
|
assert test2_roots == combined_included_roots[test2.path]
|
|
|
|
|
|
def test_env_roots_marked_explicit(install_mockery, mock_fetch):
|
|
install = SpackCommand("install")
|
|
install("dependent-install")
|
|
|
|
# Check one explicit, one implicit install
|
|
dependent = spack.store.STORE.db.query(explicit=True)
|
|
dependency = spack.store.STORE.db.query(explicit=False)
|
|
assert len(dependent) == 1
|
|
assert len(dependency) == 1
|
|
|
|
env("create", "test")
|
|
with ev.read("test") as e:
|
|
# make implicit install a root of the env
|
|
e.add(dependency[0].name)
|
|
e.concretize()
|
|
e.install_all()
|
|
|
|
explicit = spack.store.STORE.db.query(explicit=True)
|
|
assert len(explicit) == 2
|
|
|
|
|
|
def test_env_modifications_error_on_activate(install_mockery, mock_fetch, monkeypatch, capfd):
|
|
env("create", "test")
|
|
install = SpackCommand("install")
|
|
|
|
e = ev.read("test")
|
|
with e:
|
|
install("--add", "cmake-client")
|
|
|
|
def setup_error(pkg, env):
|
|
raise RuntimeError("cmake-client had issues!")
|
|
|
|
pkg = spack.repo.PATH.get_pkg_class("cmake-client")
|
|
monkeypatch.setattr(pkg, "setup_run_environment", setup_error)
|
|
|
|
spack.environment.shell.activate(e)
|
|
|
|
_, err = capfd.readouterr()
|
|
assert "cmake-client had issues!" in err
|
|
assert "Warning: could not load runtime environment" in err
|
|
|
|
|
|
def test_activate_adds_transitive_run_deps_to_path(install_mockery, mock_fetch, monkeypatch):
|
|
env("create", "test")
|
|
install = SpackCommand("install")
|
|
|
|
e = ev.read("test")
|
|
with e:
|
|
install("--add", "depends-on-run-env")
|
|
|
|
env_variables = {}
|
|
spack.environment.shell.activate(e).apply_modifications(env_variables)
|
|
assert env_variables["DEPENDENCY_ENV_VAR"] == "1"
|
|
|
|
|
|
def test_env_definition_symlink(install_mockery, mock_fetch, tmpdir):
|
|
filepath = str(tmpdir.join("spack.yaml"))
|
|
filepath_mid = str(tmpdir.join("spack_mid.yaml"))
|
|
|
|
env("create", "test")
|
|
e = ev.read("test")
|
|
e.add("mpileaks")
|
|
|
|
os.rename(e.manifest_path, filepath)
|
|
os.symlink(filepath, filepath_mid)
|
|
os.symlink(filepath_mid, e.manifest_path)
|
|
|
|
e.concretize()
|
|
e.write()
|
|
|
|
assert os.path.islink(e.manifest_path)
|
|
assert os.path.islink(filepath_mid)
|
|
|
|
|
|
def test_env_install_two_specs_same_dep(install_mockery, mock_fetch, tmpdir, capsys):
|
|
"""Test installation of two packages that share a dependency with no
|
|
connection and the second specifying the dependency as a 'build'
|
|
dependency.
|
|
"""
|
|
path = tmpdir.join("spack.yaml")
|
|
|
|
with tmpdir.as_cwd():
|
|
with open(str(path), "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
specs:
|
|
- pkg-a
|
|
- depb
|
|
"""
|
|
)
|
|
|
|
env("create", "test", "spack.yaml")
|
|
|
|
with ev.read("test"):
|
|
with capsys.disabled():
|
|
out = install()
|
|
|
|
# Ensure both packages reach install phase processing and are installed
|
|
out = str(out)
|
|
assert "depb: Executing phase:" in out
|
|
assert "a: Executing phase:" in out
|
|
|
|
depb = spack.store.STORE.db.query_one("depb", installed=True)
|
|
assert depb, "Expected depb to be installed"
|
|
|
|
a = spack.store.STORE.db.query_one("pkg-a", installed=True)
|
|
assert a, "Expected pkg-a to be installed"
|
|
|
|
|
|
def test_remove_after_concretize():
|
|
e = ev.create("test")
|
|
|
|
e.add("mpileaks")
|
|
e.concretize()
|
|
|
|
e.add("python")
|
|
e.concretize()
|
|
|
|
e.remove("mpileaks")
|
|
assert Spec("mpileaks") not in e.user_specs
|
|
env_specs = e._get_environment_specs()
|
|
assert any(s.name == "mpileaks" for s in env_specs)
|
|
|
|
e.add("mpileaks")
|
|
assert any(s.name == "mpileaks" for s in e.user_specs)
|
|
|
|
e.remove("mpileaks", force=True)
|
|
assert Spec("mpileaks") not in e.user_specs
|
|
env_specs = e._get_environment_specs()
|
|
assert not any(s.name == "mpileaks" for s in env_specs)
|
|
|
|
|
|
def test_remove_before_concretize():
|
|
e = ev.create("test")
|
|
e.unify = True
|
|
|
|
e.add("mpileaks")
|
|
e.concretize()
|
|
|
|
e.remove("mpileaks")
|
|
e.concretize()
|
|
|
|
assert not list(e.concretized_specs())
|
|
|
|
|
|
def test_remove_command():
|
|
env("create", "test")
|
|
assert "test" in env("list")
|
|
|
|
with ev.read("test"):
|
|
add("mpileaks")
|
|
assert "mpileaks" in find()
|
|
assert "mpileaks@" not in find()
|
|
assert "mpileaks@" not in find("--show-concretized")
|
|
|
|
with ev.read("test"):
|
|
remove("mpileaks")
|
|
assert "mpileaks" not in find()
|
|
assert "mpileaks@" not in find()
|
|
assert "mpileaks@" not in find("--show-concretized")
|
|
|
|
with ev.read("test"):
|
|
add("mpileaks")
|
|
assert "mpileaks" in find()
|
|
assert "mpileaks@" not in find()
|
|
assert "mpileaks@" not in find("--show-concretized")
|
|
|
|
with ev.read("test"):
|
|
concretize()
|
|
assert "mpileaks" in find()
|
|
assert "mpileaks@" not in find()
|
|
assert "mpileaks@" in find("--show-concretized")
|
|
|
|
with ev.read("test"):
|
|
remove("mpileaks")
|
|
assert "mpileaks" not in find()
|
|
# removed but still in last concretized specs
|
|
assert "mpileaks@" in find("--show-concretized")
|
|
|
|
with ev.read("test"):
|
|
concretize()
|
|
assert "mpileaks" not in find()
|
|
assert "mpileaks@" not in find()
|
|
# now the lockfile is regenerated and it's gone.
|
|
assert "mpileaks@" not in find("--show-concretized")
|
|
|
|
|
|
def test_bad_remove_included_env():
|
|
env("create", "test")
|
|
test = ev.read("test")
|
|
|
|
with test:
|
|
add("mpileaks")
|
|
|
|
test.concretize()
|
|
test.write()
|
|
|
|
env("create", "--include-concrete", "test", "combined_env")
|
|
|
|
with pytest.raises(SpackCommandError):
|
|
env("remove", "test")
|
|
|
|
|
|
def test_force_remove_included_env():
|
|
env("create", "test")
|
|
test = ev.read("test")
|
|
|
|
with test:
|
|
add("mpileaks")
|
|
|
|
test.concretize()
|
|
test.write()
|
|
|
|
env("create", "--include-concrete", "test", "combined_env")
|
|
|
|
rm_output = env("remove", "-f", "-y", "test")
|
|
list_output = env("list")
|
|
|
|
assert '"test" is being used by environment "combined_env"' in rm_output
|
|
assert "test" not in list_output
|
|
|
|
|
|
def test_environment_status(capsys, tmpdir):
|
|
with tmpdir.as_cwd():
|
|
with capsys.disabled():
|
|
assert "No active environment" in env("status")
|
|
|
|
with ev.create("test"):
|
|
with capsys.disabled():
|
|
assert "In environment test" in env("status")
|
|
|
|
with ev.create_in_dir("local_dir"):
|
|
with capsys.disabled():
|
|
assert os.path.join(os.getcwd(), "local_dir") in env("status")
|
|
|
|
e = ev.create_in_dir("myproject")
|
|
e.write()
|
|
with tmpdir.join("myproject").as_cwd():
|
|
with e:
|
|
with capsys.disabled():
|
|
assert "in current directory" in env("status")
|
|
|
|
|
|
def test_env_status_broken_view(
|
|
mutable_mock_env_path,
|
|
mock_archive,
|
|
mock_fetch,
|
|
mock_custom_repository,
|
|
install_mockery,
|
|
tmp_path,
|
|
):
|
|
with ev.create_in_dir(tmp_path):
|
|
install("--add", "trivial-install-test-package")
|
|
|
|
# switch to a new repo that doesn't include the installed package
|
|
# test that Spack detects the missing package and warns the user
|
|
with spack.repo.use_repositories(mock_custom_repository):
|
|
with ev.Environment(tmp_path):
|
|
output = env("status")
|
|
assert "includes out of date packages or repos" in output
|
|
|
|
# Test that the warning goes away when it's fixed
|
|
with ev.Environment(tmp_path):
|
|
output = env("status")
|
|
assert "includes out of date packages or repos" not in output
|
|
|
|
|
|
def test_env_activate_broken_view(
|
|
mutable_mock_env_path, mock_archive, mock_fetch, mock_custom_repository, install_mockery
|
|
):
|
|
with ev.create("test"):
|
|
install("--add", "trivial-install-test-package")
|
|
|
|
# switch to a new repo that doesn't include the installed package
|
|
# test that Spack detects the missing package and fails gracefully
|
|
with spack.repo.use_repositories(mock_custom_repository):
|
|
wrong_repo = env("activate", "--sh", "test")
|
|
assert "Warning: could not load runtime environment" in wrong_repo
|
|
assert "Unknown namespace: builtin.mock" in wrong_repo
|
|
|
|
# test replacing repo fixes it
|
|
normal_repo = env("activate", "--sh", "test")
|
|
assert "Warning: could not load runtime environment" not in normal_repo
|
|
assert "Unknown namespace: builtin.mock" not in normal_repo
|
|
|
|
|
|
def test_to_lockfile_dict():
|
|
e = ev.create("test")
|
|
e.add("mpileaks")
|
|
e.concretize()
|
|
context_dict = e._to_lockfile_dict()
|
|
|
|
e_copy = ev.create("test_copy")
|
|
|
|
e_copy._read_lockfile_dict(context_dict)
|
|
assert e.specs_by_hash == e_copy.specs_by_hash
|
|
|
|
|
|
def test_env_repo():
|
|
e = ev.create("test")
|
|
e.add("mpileaks")
|
|
e.write()
|
|
|
|
with ev.read("test"):
|
|
concretize()
|
|
|
|
pkg_cls = e.repo.get_pkg_class("mpileaks")
|
|
assert pkg_cls.name == "mpileaks"
|
|
assert pkg_cls.namespace == "builtin.mock"
|
|
|
|
|
|
def test_user_removed_spec(environment_from_manifest):
|
|
"""Ensure a user can remove from any position in the spack.yaml file."""
|
|
before = environment_from_manifest(
|
|
"""\
|
|
spack:
|
|
specs:
|
|
- mpileaks
|
|
- hypre
|
|
- libelf
|
|
"""
|
|
)
|
|
before.concretize()
|
|
before.write()
|
|
|
|
# user modifies yaml externally to spack and removes hypre
|
|
with open(before.manifest_path, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
specs:
|
|
- mpileaks
|
|
- libelf
|
|
"""
|
|
)
|
|
|
|
after = ev.read("test")
|
|
after.concretize()
|
|
after.write()
|
|
|
|
read = ev.read("test")
|
|
env_specs = read._get_environment_specs()
|
|
|
|
assert not any(x.name == "hypre" for x in env_specs)
|
|
|
|
|
|
def test_init_from_lockfile(environment_from_manifest):
|
|
"""Test that an environment can be instantiated from a lockfile."""
|
|
e1 = environment_from_manifest(
|
|
"""
|
|
spack:
|
|
specs:
|
|
- mpileaks
|
|
- hypre
|
|
- libelf
|
|
"""
|
|
)
|
|
e1.concretize()
|
|
e1.write()
|
|
|
|
e2 = _env_create("test2", init_file=e1.lock_path)
|
|
|
|
for s1, s2 in zip(e1.user_specs, e2.user_specs):
|
|
assert s1 == s2
|
|
|
|
for h1, h2 in zip(e1.concretized_order, e2.concretized_order):
|
|
assert h1 == h2
|
|
assert e1.specs_by_hash[h1] == e2.specs_by_hash[h2]
|
|
|
|
for s1, s2 in zip(e1.concretized_user_specs, e2.concretized_user_specs):
|
|
assert s1 == s2
|
|
|
|
|
|
def test_init_from_yaml(environment_from_manifest):
|
|
"""Test that an environment can be instantiated from a lockfile."""
|
|
e1 = environment_from_manifest(
|
|
"""
|
|
spack:
|
|
specs:
|
|
- mpileaks
|
|
- hypre
|
|
- libelf
|
|
"""
|
|
)
|
|
e1.concretize()
|
|
e1.write()
|
|
|
|
e2 = _env_create("test2", init_file=e1.manifest_path)
|
|
|
|
for s1, s2 in zip(e1.user_specs, e2.user_specs):
|
|
assert s1 == s2
|
|
|
|
assert not e2.concretized_order
|
|
assert not e2.concretized_user_specs
|
|
assert not e2.specs_by_hash
|
|
|
|
|
|
def test_env_view_external_prefix(tmp_path, mutable_database, mock_packages):
|
|
fake_prefix = tmp_path / "a-prefix"
|
|
fake_bin = fake_prefix / "bin"
|
|
fake_bin.mkdir(parents=True, exist_ok=False)
|
|
|
|
manifest_dir = tmp_path / "environment"
|
|
manifest_dir.mkdir(parents=True, exist_ok=False)
|
|
manifest_file = manifest_dir / ev.manifest_name
|
|
manifest_file.write_text(
|
|
"""\
|
|
spack:
|
|
specs:
|
|
- pkg-a
|
|
view: true
|
|
"""
|
|
)
|
|
|
|
external_config = io.StringIO(
|
|
"""\
|
|
packages:
|
|
pkg-a:
|
|
externals:
|
|
- spec: pkg-a@2.0
|
|
prefix: {a_prefix}
|
|
buildable: false
|
|
""".format(
|
|
a_prefix=str(fake_prefix)
|
|
)
|
|
)
|
|
external_config_dict = spack.util.spack_yaml.load_config(external_config)
|
|
|
|
test_scope = spack.config.InternalConfigScope("env-external-test", data=external_config_dict)
|
|
with spack.config.override(test_scope):
|
|
e = ev.create("test", manifest_file)
|
|
e.concretize()
|
|
# Note: normally installing specs in a test environment requires doing
|
|
# a fake install, but not for external specs since no actions are
|
|
# taken to install them. The installation commands also include
|
|
# post-installation functions like DB-registration, so are important
|
|
# to do (otherwise the package is not considered installed).
|
|
e.install_all()
|
|
e.write()
|
|
|
|
env_mod = spack.util.environment.EnvironmentModifications()
|
|
e.add_view_to_env(env_mod, "default")
|
|
env_variables = {}
|
|
env_mod.apply_modifications(env_variables)
|
|
assert str(fake_bin) in env_variables["PATH"]
|
|
|
|
|
|
def test_init_with_file_and_remove(tmpdir):
|
|
"""Ensure a user can remove from any position in the spack.yaml file."""
|
|
path = tmpdir.join("spack.yaml")
|
|
|
|
with tmpdir.as_cwd():
|
|
with open(str(path), "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
specs:
|
|
- mpileaks
|
|
"""
|
|
)
|
|
|
|
env("create", "test", "spack.yaml")
|
|
|
|
out = env("list")
|
|
assert "test" in out
|
|
|
|
with ev.read("test"):
|
|
assert "mpileaks" in find()
|
|
|
|
env("remove", "-y", "test")
|
|
|
|
out = env("list")
|
|
assert "test" not in out
|
|
|
|
|
|
def test_env_with_config(environment_from_manifest):
|
|
e = environment_from_manifest(
|
|
"""
|
|
spack:
|
|
specs:
|
|
- mpileaks
|
|
packages:
|
|
mpileaks:
|
|
version: ["2.2"]
|
|
"""
|
|
)
|
|
with e:
|
|
e.concretize()
|
|
|
|
assert any(x.intersects("mpileaks@2.2") for x in e._get_environment_specs())
|
|
|
|
|
|
def test_with_config_bad_include_create(environment_from_manifest):
|
|
"""Confirm missing include paths raise expected exception and error."""
|
|
with pytest.raises(spack.config.ConfigFileError, match="2 missing include path"):
|
|
environment_from_manifest(
|
|
"""
|
|
spack:
|
|
include:
|
|
- /no/such/directory
|
|
- no/such/file.yaml
|
|
"""
|
|
)
|
|
|
|
|
|
def test_with_config_bad_include_activate(environment_from_manifest, tmpdir):
|
|
env_root = pathlib.Path(tmpdir.ensure("env-root", dir=True))
|
|
include1 = env_root / "include1.yaml"
|
|
include1.touch()
|
|
|
|
abs_include_path = os.path.abspath(tmpdir.join("subdir").ensure("include2.yaml"))
|
|
|
|
spack_yaml = env_root / ev.manifest_name
|
|
spack_yaml.write_text(
|
|
f"""
|
|
spack:
|
|
include:
|
|
- ./include1.yaml
|
|
- {abs_include_path}
|
|
"""
|
|
)
|
|
|
|
with ev.Environment(env_root) as e:
|
|
e.concretize()
|
|
|
|
# we've created an environment with some included config files (which do
|
|
# in fact exist): now we remove them and check that we get a sensible
|
|
# error message
|
|
|
|
os.remove(abs_include_path)
|
|
os.remove(include1)
|
|
with pytest.raises(spack.config.ConfigFileError) as exc:
|
|
ev.activate(ev.Environment(env_root))
|
|
|
|
err = exc.value.message
|
|
assert "missing include" in err
|
|
assert abs_include_path in err
|
|
assert "include1.yaml" in err
|
|
assert ev.active_environment() is None
|
|
|
|
|
|
def test_env_with_include_config_files_same_basename(tmp_path, environment_from_manifest):
|
|
file1 = fs.join_path(tmp_path, "path", "to", "included-config.yaml")
|
|
fs.mkdirp(os.path.dirname(file1))
|
|
with open(file1, "w") as f:
|
|
f.write(
|
|
"""\
|
|
packages:
|
|
libelf:
|
|
version: ["0.8.10"]
|
|
"""
|
|
)
|
|
|
|
file2 = fs.join_path(tmp_path, "second", "path", "included-config.yaml")
|
|
fs.mkdirp(os.path.dirname(file2))
|
|
with open(file2, "w") as f:
|
|
f.write(
|
|
"""\
|
|
packages:
|
|
mpileaks:
|
|
version: ["2.2"]
|
|
"""
|
|
)
|
|
|
|
e = environment_from_manifest(
|
|
f"""
|
|
spack:
|
|
include:
|
|
- {file1}
|
|
- {file2}
|
|
specs:
|
|
- libelf
|
|
- mpileaks
|
|
"""
|
|
)
|
|
|
|
with e:
|
|
e.concretize()
|
|
|
|
environment_specs = e._get_environment_specs(False)
|
|
|
|
assert environment_specs[0].satisfies("libelf@0.8.10")
|
|
assert environment_specs[1].satisfies("mpileaks@2.2")
|
|
|
|
|
|
@pytest.fixture(scope="function")
|
|
def packages_file(tmpdir):
|
|
"""Return the path to the packages configuration file."""
|
|
raw_yaml = """
|
|
packages:
|
|
mpileaks:
|
|
version: ["2.2"]
|
|
"""
|
|
filename = tmpdir.ensure("testconfig", "packages.yaml")
|
|
filename.write(raw_yaml)
|
|
yield filename
|
|
|
|
|
|
def mpileaks_env_config(include_path):
|
|
"""Return the contents of an environment that includes the provided
|
|
path and lists mpileaks as the sole spec."""
|
|
return """\
|
|
spack:
|
|
include:
|
|
- {0}
|
|
specs:
|
|
- mpileaks
|
|
""".format(
|
|
include_path
|
|
)
|
|
|
|
|
|
def test_env_with_included_config_file(mutable_mock_env_path, packages_file):
|
|
"""Test inclusion of a relative packages configuration file added to an
|
|
existing environment.
|
|
"""
|
|
env_root = mutable_mock_env_path
|
|
fs.mkdirp(env_root)
|
|
include_filename = "included-config.yaml"
|
|
included_path = env_root / include_filename
|
|
shutil.move(packages_file.strpath, included_path)
|
|
|
|
spack_yaml = env_root / ev.manifest_name
|
|
spack_yaml.write_text(
|
|
f"""\
|
|
spack:
|
|
include:
|
|
- {os.path.join(".", include_filename)}
|
|
specs:
|
|
- mpileaks
|
|
"""
|
|
)
|
|
|
|
e = ev.Environment(env_root)
|
|
with e:
|
|
e.concretize()
|
|
|
|
assert any(x.satisfies("mpileaks@2.2") for x in e._get_environment_specs())
|
|
|
|
|
|
def test_config_change_existing(mutable_mock_env_path, tmp_path, mock_packages, mutable_config):
|
|
"""Test ``config change`` with config in the ``spack.yaml`` as well as an
|
|
included file scope.
|
|
"""
|
|
|
|
included_file = "included-packages.yaml"
|
|
included_path = tmp_path / included_file
|
|
with open(included_path, "w") as f:
|
|
f.write(
|
|
"""\
|
|
packages:
|
|
mpich:
|
|
require:
|
|
- spec: "@3.0.2"
|
|
libelf:
|
|
require: "@0.8.10"
|
|
bowtie:
|
|
require:
|
|
- one_of: ["@1.3.0", "@1.2.0"]
|
|
"""
|
|
)
|
|
|
|
spack_yaml = tmp_path / ev.manifest_name
|
|
spack_yaml.write_text(
|
|
f"""\
|
|
spack:
|
|
packages:
|
|
mpich:
|
|
require:
|
|
- spec: "+debug"
|
|
include:
|
|
- {os.path.join(".", included_file)}
|
|
specs: []
|
|
"""
|
|
)
|
|
|
|
e = ev.Environment(tmp_path)
|
|
with e:
|
|
# List of requirements, flip a variant
|
|
config("change", "packages:mpich:require:~debug")
|
|
test_spec = spack.spec.Spec("mpich").concretized()
|
|
assert test_spec.satisfies("@3.0.2~debug")
|
|
|
|
# List of requirements, change the version (in a different scope)
|
|
config("change", "packages:mpich:require:@3.0.3")
|
|
test_spec = spack.spec.Spec("mpich").concretized()
|
|
assert test_spec.satisfies("@3.0.3")
|
|
|
|
# "require:" as a single string, also try specifying
|
|
# a spec string that requires enclosing in quotes as
|
|
# part of the config path
|
|
config("change", 'packages:libelf:require:"@0.8.12:"')
|
|
spack.spec.Spec("libelf@0.8.12").concretized()
|
|
# No need for assert, if there wasn't a failure, we
|
|
# changed the requirement successfully.
|
|
|
|
# Use change to add a requirement for a package that
|
|
# has no requirements defined
|
|
config("change", "packages:fftw:require:+mpi")
|
|
test_spec = spack.spec.Spec("fftw").concretized()
|
|
assert test_spec.satisfies("+mpi")
|
|
config("change", "packages:fftw:require:~mpi")
|
|
test_spec = spack.spec.Spec("fftw").concretized()
|
|
assert test_spec.satisfies("~mpi")
|
|
config("change", "packages:fftw:require:@1.0")
|
|
test_spec = spack.spec.Spec("fftw").concretized()
|
|
assert test_spec.satisfies("@1.0~mpi")
|
|
|
|
# Use "--match-spec" to change one spec in a "one_of"
|
|
# list
|
|
config("change", "packages:bowtie:require:@1.2.2", "--match-spec", "@1.2.0")
|
|
spack.spec.Spec("bowtie@1.3.0").concretize()
|
|
spack.spec.Spec("bowtie@1.2.2").concretized()
|
|
|
|
|
|
def test_config_change_new(mutable_mock_env_path, tmp_path, mock_packages, mutable_config):
|
|
spack_yaml = tmp_path / ev.manifest_name
|
|
spack_yaml.write_text(
|
|
"""\
|
|
spack:
|
|
specs: []
|
|
"""
|
|
)
|
|
|
|
with ev.Environment(tmp_path):
|
|
config("change", "packages:mpich:require:~debug")
|
|
with pytest.raises(spack.solver.asp.UnsatisfiableSpecError):
|
|
spack.spec.Spec("mpich+debug").concretized()
|
|
spack.spec.Spec("mpich~debug").concretized()
|
|
|
|
# Now check that we raise an error if we need to add a require: constraint
|
|
# when preexisting config manually specified it as a singular spec
|
|
spack_yaml.write_text(
|
|
"""\
|
|
spack:
|
|
specs: []
|
|
packages:
|
|
mpich:
|
|
require: "@3.0.3"
|
|
"""
|
|
)
|
|
with ev.Environment(tmp_path):
|
|
assert spack.spec.Spec("mpich").concretized().satisfies("@3.0.3")
|
|
with pytest.raises(spack.config.ConfigError, match="not a list"):
|
|
config("change", "packages:mpich:require:~debug")
|
|
|
|
|
|
def test_env_with_included_config_file_url(tmpdir, mutable_empty_config, packages_file):
|
|
"""Test configuration inclusion of a file whose path is a URL before
|
|
the environment is concretized."""
|
|
|
|
spack_yaml = tmpdir.join("spack.yaml")
|
|
with spack_yaml.open("w") as f:
|
|
f.write("spack:\n include:\n - file://{0}\n".format(packages_file))
|
|
|
|
env = ev.Environment(tmpdir.strpath)
|
|
ev.activate(env)
|
|
|
|
cfg = spack.config.get("packages")
|
|
assert cfg["mpileaks"]["version"] == ["2.2"]
|
|
|
|
|
|
def test_env_with_included_config_missing_file(tmpdir, mutable_empty_config):
|
|
"""Test inclusion of a missing configuration file raises FetchError
|
|
noting missing file."""
|
|
|
|
spack_yaml = tmpdir.join("spack.yaml")
|
|
missing_file = tmpdir.join("packages.yaml")
|
|
with spack_yaml.open("w") as f:
|
|
f.write("spack:\n include:\n - {0}\n".format(missing_file.strpath))
|
|
|
|
with pytest.raises(spack.config.ConfigError, match="missing include path"):
|
|
ev.Environment(tmpdir.strpath)
|
|
|
|
|
|
def test_env_with_included_config_scope(mutable_mock_env_path, packages_file):
|
|
"""Test inclusion of a package file from the environment's configuration
|
|
stage directory. This test is intended to represent a case where a remote
|
|
file has already been staged."""
|
|
env_root = mutable_mock_env_path
|
|
config_scope_path = env_root / "config"
|
|
|
|
# Copy the packages.yaml file to the environment configuration
|
|
# directory, so it is picked up during concretization. (Using
|
|
# copy instead of rename in case the fixture scope changes.)
|
|
fs.mkdirp(config_scope_path)
|
|
include_filename = os.path.basename(packages_file.strpath)
|
|
included_path = config_scope_path / include_filename
|
|
fs.copy(packages_file.strpath, included_path)
|
|
|
|
# Configure the environment to include file(s) from the environment's
|
|
# remote configuration stage directory.
|
|
spack_yaml = env_root / ev.manifest_name
|
|
spack_yaml.write_text(mpileaks_env_config(config_scope_path))
|
|
|
|
# Ensure the concretized environment reflects contents of the
|
|
# packages.yaml file.
|
|
e = ev.Environment(env_root)
|
|
with e:
|
|
e.concretize()
|
|
|
|
assert any(x.satisfies("mpileaks@2.2") for x in e._get_environment_specs())
|
|
|
|
|
|
def test_env_with_included_config_var_path(tmpdir, packages_file):
|
|
"""Test inclusion of a package configuration file with path variables
|
|
"staged" in the environment's configuration stage directory."""
|
|
included_file = packages_file.strpath
|
|
env_path = pathlib.PosixPath(tmpdir)
|
|
config_var_path = os.path.join("$tempdir", "included-packages.yaml")
|
|
|
|
spack_yaml = env_path / ev.manifest_name
|
|
spack_yaml.write_text(mpileaks_env_config(config_var_path))
|
|
|
|
config_real_path = substitute_path_variables(config_var_path)
|
|
shutil.move(included_file, config_real_path)
|
|
assert os.path.exists(config_real_path)
|
|
|
|
e = ev.Environment(env_path)
|
|
with e:
|
|
e.concretize()
|
|
|
|
assert any(x.satisfies("mpileaks@2.2") for x in e._get_environment_specs())
|
|
|
|
|
|
def test_env_with_included_config_precedence(tmp_path):
|
|
"""Test included scope and manifest precedence when including a package
|
|
configuration file."""
|
|
|
|
included_file = "included-packages.yaml"
|
|
included_path = tmp_path / included_file
|
|
with open(included_path, "w") as f:
|
|
f.write(
|
|
"""\
|
|
packages:
|
|
mpileaks:
|
|
version: ["2.2"]
|
|
libelf:
|
|
version: ["0.8.10"]
|
|
"""
|
|
)
|
|
|
|
spack_yaml = tmp_path / ev.manifest_name
|
|
spack_yaml.write_text(
|
|
f"""\
|
|
spack:
|
|
packages:
|
|
libelf:
|
|
version: ["0.8.12"]
|
|
include:
|
|
- {os.path.join(".", included_file)}
|
|
specs:
|
|
- mpileaks
|
|
"""
|
|
)
|
|
|
|
e = ev.Environment(tmp_path)
|
|
with e:
|
|
e.concretize()
|
|
specs = e._get_environment_specs()
|
|
|
|
# ensure included scope took effect
|
|
assert any(x.satisfies("mpileaks@2.2") for x in specs)
|
|
|
|
# ensure env file takes precedence
|
|
assert any(x.satisfies("libelf@0.8.12") for x in specs)
|
|
|
|
|
|
def test_env_with_included_configs_precedence(tmp_path):
|
|
"""Test precendence of multiple included configuration files."""
|
|
file1 = "high-config.yaml"
|
|
file2 = "low-config.yaml"
|
|
|
|
spack_yaml = tmp_path / ev.manifest_name
|
|
spack_yaml.write_text(
|
|
f"""\
|
|
spack:
|
|
include:
|
|
- {os.path.join(".", file1)} # this one should take precedence
|
|
- {os.path.join(".", file2)}
|
|
specs:
|
|
- mpileaks
|
|
"""
|
|
)
|
|
|
|
with open(tmp_path / file1, "w") as f:
|
|
f.write(
|
|
"""\
|
|
packages:
|
|
libelf:
|
|
version: ["0.8.10"] # this should override libelf version below
|
|
"""
|
|
)
|
|
|
|
with open(tmp_path / file2, "w") as f:
|
|
f.write(
|
|
"""\
|
|
packages:
|
|
mpileaks:
|
|
version: ["2.2"]
|
|
libelf:
|
|
version: ["0.8.12"]
|
|
"""
|
|
)
|
|
|
|
e = ev.Environment(tmp_path)
|
|
with e:
|
|
e.concretize()
|
|
specs = e._get_environment_specs()
|
|
|
|
# ensure included package spec took precedence over manifest spec
|
|
assert any(x.satisfies("mpileaks@2.2") for x in specs)
|
|
|
|
# ensure first included package spec took precedence over one from second
|
|
assert any(x.satisfies("libelf@0.8.10") for x in specs)
|
|
|
|
|
|
@pytest.mark.regression("39248")
|
|
def test_bad_env_yaml_format_remove(mutable_mock_env_path):
|
|
badenv = "badenv"
|
|
env("create", badenv)
|
|
filename = mutable_mock_env_path / "spack.yaml"
|
|
with open(filename, "w") as f:
|
|
f.write(
|
|
"""\
|
|
- mpileaks
|
|
"""
|
|
)
|
|
|
|
assert badenv in env("list")
|
|
env("remove", "-y", badenv)
|
|
assert badenv not in env("list")
|
|
|
|
|
|
@pytest.mark.regression("39248")
|
|
@pytest.mark.parametrize(
|
|
"error,message,contents",
|
|
[
|
|
(
|
|
spack.config.ConfigFormatError,
|
|
"not of type",
|
|
"""\
|
|
spack:
|
|
specs: mpi@2.0
|
|
""",
|
|
),
|
|
(
|
|
ev.SpackEnvironmentConfigError,
|
|
"duplicate key",
|
|
"""\
|
|
spack:
|
|
packages:
|
|
all:
|
|
providers:
|
|
mpi: [mvapich2]
|
|
mpi: [mpich]
|
|
""",
|
|
),
|
|
(
|
|
spack.config.ConfigFormatError,
|
|
"'specks' was unexpected",
|
|
"""\
|
|
spack:
|
|
specks:
|
|
- libdwarf
|
|
""",
|
|
),
|
|
],
|
|
)
|
|
def test_bad_env_yaml_create_fails(tmp_path, mutable_mock_env_path, error, message, contents):
|
|
"""Ensure creation with invalid yaml does NOT create or leave the environment."""
|
|
filename = tmp_path / ev.manifest_name
|
|
filename.write_text(contents)
|
|
env_name = "bad_env"
|
|
with pytest.raises(error, match=message):
|
|
env("create", env_name, str(filename))
|
|
|
|
assert env_name not in env("list")
|
|
manifest = mutable_mock_env_path / env_name / ev.manifest_name
|
|
assert not os.path.exists(str(manifest))
|
|
|
|
|
|
@pytest.mark.regression("39248")
|
|
@pytest.mark.parametrize("answer", ["-y", ""])
|
|
def test_multi_env_remove(mutable_mock_env_path, monkeypatch, answer):
|
|
"""Test removal (or not) of a valid and invalid environment"""
|
|
remove_environment = answer == "-y"
|
|
monkeypatch.setattr(tty, "get_yes_or_no", lambda prompt, default: remove_environment)
|
|
|
|
environments = ["goodenv", "badenv"]
|
|
for e in environments:
|
|
env("create", e)
|
|
|
|
# Ensure the bad environment contains invalid yaml
|
|
filename = mutable_mock_env_path / environments[1] / ev.manifest_name
|
|
filename.write_text(
|
|
"""\
|
|
- libdwarf
|
|
"""
|
|
)
|
|
|
|
assert all(e in env("list") for e in environments)
|
|
|
|
args = [answer] if answer else []
|
|
args.extend(environments)
|
|
output = env("remove", *args, fail_on_error=False)
|
|
|
|
if remove_environment is True:
|
|
# Successfully removed (and reported removal) of *both* environments
|
|
assert not all(e in env("list") for e in environments)
|
|
assert output.count("Successfully removed") == len(environments)
|
|
else:
|
|
# Not removing any of the environments
|
|
assert all(e in env("list") for e in environments)
|
|
|
|
|
|
def test_env_loads(install_mockery, mock_fetch, mock_modules_root):
|
|
env("create", "test")
|
|
|
|
with ev.read("test"):
|
|
add("mpileaks")
|
|
concretize()
|
|
install("--fake")
|
|
module("tcl", "refresh", "-y")
|
|
|
|
with ev.read("test"):
|
|
env("loads")
|
|
|
|
e = ev.read("test")
|
|
|
|
loads_file = os.path.join(e.path, "loads")
|
|
assert os.path.exists(loads_file)
|
|
|
|
with open(loads_file) as f:
|
|
contents = f.read()
|
|
assert "module load mpileaks" in contents
|
|
|
|
|
|
@pytest.mark.disable_clean_stage_check
|
|
def test_stage(mock_stage, mock_fetch, install_mockery):
|
|
env("create", "test")
|
|
with ev.read("test"):
|
|
add("mpileaks")
|
|
add("zmpi")
|
|
concretize()
|
|
stage()
|
|
|
|
root = str(mock_stage)
|
|
|
|
def check_stage(spec):
|
|
spec = Spec(spec).concretized()
|
|
for dep in spec.traverse():
|
|
stage_name = "{0}{1}-{2}-{3}".format(
|
|
stage_prefix, dep.name, dep.version, dep.dag_hash()
|
|
)
|
|
assert os.path.isdir(os.path.join(root, stage_name))
|
|
|
|
check_stage("mpileaks")
|
|
check_stage("zmpi")
|
|
|
|
|
|
def test_env_commands_die_with_no_env_arg():
|
|
# these fail in argparse when given no arg
|
|
with pytest.raises(SystemExit):
|
|
env("create")
|
|
with pytest.raises(SystemExit):
|
|
env("remove")
|
|
|
|
# these have an optional env arg and raise errors via tty.die
|
|
with pytest.raises(SpackCommandError):
|
|
env("loads")
|
|
|
|
# This should NOT raise an error with no environment
|
|
# it just tells the user there isn't an environment
|
|
env("status")
|
|
|
|
|
|
def test_env_blocks_uninstall(mock_stage, mock_fetch, install_mockery):
|
|
env("create", "test")
|
|
with ev.read("test"):
|
|
add("mpileaks")
|
|
install("--fake")
|
|
|
|
out = uninstall("-y", "mpileaks", fail_on_error=False)
|
|
assert uninstall.returncode == 1
|
|
assert "The following environments still reference these specs" in out
|
|
|
|
|
|
def test_roots_display_with_variants():
|
|
env("create", "test")
|
|
with ev.read("test"):
|
|
add("boost+shared")
|
|
|
|
with ev.read("test"):
|
|
out = find(output=str)
|
|
|
|
assert "boost +shared" in out
|
|
|
|
|
|
def test_uninstall_keeps_in_env(mock_stage, mock_fetch, install_mockery):
|
|
# 'spack uninstall' without --remove should not change the environment
|
|
# spack.yaml file, just uninstall specs
|
|
env("create", "test")
|
|
with ev.read("test"):
|
|
add("mpileaks")
|
|
add("libelf")
|
|
install("--fake")
|
|
|
|
test = ev.read("test")
|
|
# Save this spec to check later if it is still in the env
|
|
(mpileaks_hash,) = list(x for x, y in test.specs_by_hash.items() if y.name == "mpileaks")
|
|
orig_user_specs = test.user_specs
|
|
orig_concretized_specs = test.concretized_order
|
|
|
|
with ev.read("test"):
|
|
uninstall("-ya")
|
|
|
|
test = ev.read("test")
|
|
assert test.concretized_order == orig_concretized_specs
|
|
assert test.user_specs.specs == orig_user_specs.specs
|
|
assert mpileaks_hash in test.specs_by_hash
|
|
assert not test.specs_by_hash[mpileaks_hash].package.installed
|
|
|
|
|
|
def test_uninstall_removes_from_env(mock_stage, mock_fetch, install_mockery):
|
|
# 'spack uninstall --remove' should update the environment
|
|
env("create", "test")
|
|
with ev.read("test"):
|
|
add("mpileaks")
|
|
add("libelf")
|
|
install("--fake")
|
|
|
|
with ev.read("test"):
|
|
uninstall("-y", "-a", "--remove")
|
|
|
|
test = ev.read("test")
|
|
assert not test.specs_by_hash
|
|
assert not test.concretized_order
|
|
assert not test.user_specs
|
|
|
|
|
|
def test_indirect_build_dep(tmp_path):
|
|
"""Simple case of X->Y->Z where Y is a build/link dep and Z is a
|
|
build-only dep. Make sure this concrete DAG is preserved when writing the
|
|
environment out and reading it back.
|
|
"""
|
|
builder = spack.repo.MockRepositoryBuilder(tmp_path / "repo")
|
|
builder.add_package("z")
|
|
builder.add_package("y", dependencies=[("z", "build", None)])
|
|
builder.add_package("x", dependencies=[("y", None, None)])
|
|
|
|
with spack.repo.use_repositories(builder.root):
|
|
x_spec = Spec("x")
|
|
x_concretized = x_spec.concretized()
|
|
|
|
_env_create("test", with_view=False)
|
|
e = ev.read("test")
|
|
e.add(x_spec)
|
|
e.concretize()
|
|
e.write()
|
|
|
|
e_read = ev.read("test")
|
|
(x_env_hash,) = e_read.concretized_order
|
|
|
|
x_env_spec = e_read.specs_by_hash[x_env_hash]
|
|
assert x_env_spec == x_concretized
|
|
|
|
|
|
def test_store_different_build_deps(tmp_path):
|
|
r"""Ensure that an environment can store two instances of a build-only
|
|
dependency::
|
|
|
|
x y
|
|
/| (l) | (b)
|
|
(b) | y z2
|
|
\| (b)
|
|
z1
|
|
|
|
"""
|
|
builder = spack.repo.MockRepositoryBuilder(tmp_path / "mirror")
|
|
builder.add_package("z")
|
|
builder.add_package("y", dependencies=[("z", "build", None)])
|
|
builder.add_package("x", dependencies=[("y", None, None), ("z", "build", None)])
|
|
|
|
with spack.repo.use_repositories(builder.root):
|
|
y_spec = Spec("y ^z@3")
|
|
y_concretized = y_spec.concretized()
|
|
|
|
x_spec = Spec("x ^z@2")
|
|
x_concretized = x_spec.concretized()
|
|
|
|
# Even though x chose a different 'z', the y it chooses should be identical
|
|
# *aside* from the dependency on 'z'. The dag_hash() will show the difference
|
|
# in build dependencies.
|
|
assert x_concretized["y"].eq_node(y_concretized)
|
|
assert x_concretized["y"].dag_hash() != y_concretized.dag_hash()
|
|
|
|
_env_create("test", with_view=False)
|
|
e = ev.read("test")
|
|
e.add(y_spec)
|
|
e.add(x_spec)
|
|
e.concretize()
|
|
e.write()
|
|
|
|
e_read = ev.read("test")
|
|
y_env_hash, x_env_hash = e_read.concretized_order
|
|
|
|
y_read = e_read.specs_by_hash[y_env_hash]
|
|
x_read = e_read.specs_by_hash[x_env_hash]
|
|
|
|
# make sure the DAG hashes and build deps are preserved after
|
|
# a round trip to/from the lockfile
|
|
assert x_read["z"] != y_read["z"]
|
|
assert x_read["z"].dag_hash() != y_read["z"].dag_hash()
|
|
|
|
assert x_read["y"].eq_node(y_read)
|
|
assert x_read["y"].dag_hash() != y_read.dag_hash()
|
|
|
|
|
|
def test_env_updates_view_install(tmpdir, mock_stage, mock_fetch, install_mockery):
|
|
view_dir = tmpdir.join("view")
|
|
env("create", "--with-view=%s" % view_dir, "test")
|
|
with ev.read("test"):
|
|
add("mpileaks")
|
|
install("--fake")
|
|
|
|
check_mpileaks_and_deps_in_view(view_dir)
|
|
|
|
|
|
def test_env_view_fails(tmpdir, mock_packages, mock_stage, mock_fetch, install_mockery):
|
|
# We currently ignore file-file conflicts for the prefix merge,
|
|
# so in principle there will be no errors in this test. But
|
|
# the .spack metadata dir is handled separately and is more strict.
|
|
# It also throws on file-file conflicts. That's what we're checking here
|
|
# by adding the same package twice to a view.
|
|
view_dir = tmpdir.join("view")
|
|
env("create", "--with-view=%s" % view_dir, "test")
|
|
with ev.read("test"):
|
|
add("libelf")
|
|
add("libelf cflags=-g")
|
|
with pytest.raises(
|
|
ev.SpackEnvironmentViewError, match="two specs project to the same prefix"
|
|
):
|
|
install("--fake")
|
|
|
|
|
|
def test_env_view_fails_dir_file(tmpdir, mock_packages, mock_stage, mock_fetch, install_mockery):
|
|
# This environment view fails to be created because a file
|
|
# and a dir are in the same path. Test that it mentions the problematic path.
|
|
view_dir = tmpdir.join("view")
|
|
env("create", "--with-view=%s" % view_dir, "test")
|
|
with ev.read("test"):
|
|
add("view-file")
|
|
add("view-dir")
|
|
with pytest.raises(
|
|
llnl.util.link_tree.MergeConflictSummary, match=os.path.join("bin", "x")
|
|
):
|
|
install()
|
|
|
|
|
|
def test_env_view_succeeds_symlinked_dir_file(
|
|
tmpdir, mock_packages, mock_stage, mock_fetch, install_mockery
|
|
):
|
|
# A symlinked dir and an ordinary dir merge happily
|
|
view_dir = tmpdir.join("view")
|
|
env("create", "--with-view=%s" % view_dir, "test")
|
|
with ev.read("test"):
|
|
add("view-symlinked-dir")
|
|
add("view-dir")
|
|
install()
|
|
x_dir = os.path.join(str(view_dir), "bin", "x")
|
|
assert os.path.exists(os.path.join(x_dir, "file_in_dir"))
|
|
assert os.path.exists(os.path.join(x_dir, "file_in_symlinked_dir"))
|
|
|
|
|
|
def test_env_without_view_install(tmpdir, mock_stage, mock_fetch, install_mockery):
|
|
# Test enabling a view after installing specs
|
|
env("create", "--without-view", "test")
|
|
|
|
test_env = ev.read("test")
|
|
with pytest.raises(ev.SpackEnvironmentError):
|
|
test_env.default_view
|
|
|
|
view_dir = tmpdir.join("view")
|
|
|
|
with ev.read("test"):
|
|
add("mpileaks")
|
|
install("--fake")
|
|
|
|
env("view", "enable", str(view_dir))
|
|
|
|
# After enabling the view, the specs should be linked into the environment
|
|
# view dir
|
|
check_mpileaks_and_deps_in_view(view_dir)
|
|
|
|
|
|
@pytest.mark.parametrize("env_name", [True, False])
|
|
def test_env_include_concrete_env_yaml(env_name):
|
|
env("create", "test")
|
|
test = ev.read("test")
|
|
|
|
with test:
|
|
add("mpileaks")
|
|
test.concretize()
|
|
test.write()
|
|
|
|
environ = "test" if env_name else test.path
|
|
|
|
env("create", "--include-concrete", environ, "combined_env")
|
|
|
|
combined = ev.read("combined_env")
|
|
combined_yaml = combined.manifest["spack"]
|
|
|
|
assert "include_concrete" in combined_yaml
|
|
assert test.path in combined_yaml["include_concrete"]
|
|
|
|
|
|
@pytest.mark.regression("45766")
|
|
@pytest.mark.parametrize("format", ["v1", "v2", "v3"])
|
|
def test_env_include_concrete_old_env(format, tmpdir):
|
|
lockfile = os.path.join(spack.paths.test_path, "data", "legacy_env", f"{format}.lock")
|
|
# create an env from old .lock file -- this does not update the format
|
|
env("create", "old-env", lockfile)
|
|
env("create", "--include-concrete", "old-env", "test")
|
|
|
|
assert ev.read("old-env").all_specs() == ev.read("test").all_specs()
|
|
|
|
|
|
def test_env_bad_include_concrete_env():
|
|
with pytest.raises(ev.SpackEnvironmentError):
|
|
env("create", "--include-concrete", "nonexistant_env", "combined_env")
|
|
|
|
|
|
def test_env_not_concrete_include_concrete_env():
|
|
env("create", "test")
|
|
test = ev.read("test")
|
|
|
|
with test:
|
|
add("mpileaks")
|
|
|
|
with pytest.raises(ev.SpackEnvironmentError):
|
|
env("create", "--include-concrete", "test", "combined_env")
|
|
|
|
|
|
def test_env_multiple_include_concrete_envs():
|
|
test1, test2, combined = setup_combined_multiple_env()
|
|
|
|
combined_yaml = combined.manifest["spack"]
|
|
|
|
assert test1.path in combined_yaml["include_concrete"][0]
|
|
assert test2.path in combined_yaml["include_concrete"][1]
|
|
|
|
# No local specs in the combined env
|
|
assert not combined_yaml["specs"]
|
|
|
|
|
|
def test_env_include_concrete_envs_lockfile():
|
|
test1, test2, combined = setup_combined_multiple_env()
|
|
|
|
combined_yaml = combined.manifest["spack"]
|
|
|
|
assert "include_concrete" in combined_yaml
|
|
assert test1.path in combined_yaml["include_concrete"]
|
|
|
|
with open(combined.lock_path) as f:
|
|
lockfile_as_dict = combined._read_lockfile(f)
|
|
|
|
assert set(
|
|
entry["hash"] for entry in lockfile_as_dict["include_concrete"][test1.path]["roots"]
|
|
) == set(test1.specs_by_hash)
|
|
assert set(
|
|
entry["hash"] for entry in lockfile_as_dict["include_concrete"][test2.path]["roots"]
|
|
) == set(test2.specs_by_hash)
|
|
|
|
|
|
def test_env_include_concrete_add_env():
|
|
test1, test2, combined = setup_combined_multiple_env()
|
|
|
|
# crete new env & crecretize
|
|
env("create", "new")
|
|
new_env = ev.read("new")
|
|
with new_env:
|
|
add("mpileaks")
|
|
|
|
new_env.concretize()
|
|
new_env.write()
|
|
|
|
# add new env to combined
|
|
combined.included_concrete_envs.append(new_env.path)
|
|
|
|
# assert thing haven't changed yet
|
|
with open(combined.lock_path) as f:
|
|
lockfile_as_dict = combined._read_lockfile(f)
|
|
|
|
assert new_env.path not in lockfile_as_dict["include_concrete"].keys()
|
|
|
|
# concretize combined env with new env
|
|
combined.concretize()
|
|
combined.write()
|
|
|
|
# assert changes
|
|
with open(combined.lock_path) as f:
|
|
lockfile_as_dict = combined._read_lockfile(f)
|
|
|
|
assert new_env.path in lockfile_as_dict["include_concrete"].keys()
|
|
|
|
|
|
def test_env_include_concrete_remove_env():
|
|
test1, test2, combined = setup_combined_multiple_env()
|
|
|
|
# remove test2 from combined
|
|
combined.included_concrete_envs = [test1.path]
|
|
|
|
# assert test2 is still in combined's lockfile
|
|
with open(combined.lock_path) as f:
|
|
lockfile_as_dict = combined._read_lockfile(f)
|
|
|
|
assert test2.path in lockfile_as_dict["include_concrete"].keys()
|
|
|
|
# reconcretize combined
|
|
combined.concretize()
|
|
combined.write()
|
|
|
|
# assert test2 is not in combined's lockfile
|
|
with open(combined.lock_path) as f:
|
|
lockfile_as_dict = combined._read_lockfile(f)
|
|
|
|
assert test2.path not in lockfile_as_dict["include_concrete"].keys()
|
|
|
|
|
|
@pytest.mark.parametrize("unify", [True, False, "when_possible"])
|
|
def test_env_include_concrete_env_reconcretized(unify):
|
|
"""Double check to make sure that concrete_specs for the local specs is empty
|
|
after recocnretizing.
|
|
"""
|
|
_, _, combined = setup_combined_multiple_env()
|
|
|
|
combined.unify = unify
|
|
|
|
with open(combined.lock_path) as f:
|
|
lockfile_as_dict = combined._read_lockfile(f)
|
|
|
|
assert not lockfile_as_dict["roots"]
|
|
assert not lockfile_as_dict["concrete_specs"]
|
|
|
|
combined.concretize()
|
|
combined.write()
|
|
|
|
with open(combined.lock_path) as f:
|
|
lockfile_as_dict = combined._read_lockfile(f)
|
|
|
|
assert not lockfile_as_dict["roots"]
|
|
assert not lockfile_as_dict["concrete_specs"]
|
|
|
|
|
|
def test_concretize_include_concrete_env():
|
|
test1, _, combined = setup_combined_multiple_env()
|
|
|
|
with test1:
|
|
add("mpileaks")
|
|
test1.concretize()
|
|
test1.write()
|
|
|
|
assert Spec("mpileaks") in test1.concretized_user_specs
|
|
assert Spec("mpileaks") not in combined.included_concretized_user_specs[test1.path]
|
|
|
|
combined.concretize()
|
|
combined.write()
|
|
|
|
assert Spec("mpileaks") in combined.included_concretized_user_specs[test1.path]
|
|
|
|
|
|
def test_concretize_nested_include_concrete_envs():
|
|
env("create", "test1")
|
|
test1 = ev.read("test1")
|
|
with test1:
|
|
add("zlib")
|
|
test1.concretize()
|
|
test1.write()
|
|
|
|
env("create", "--include-concrete", "test1", "test2")
|
|
test2 = ev.read("test2")
|
|
with test2:
|
|
add("libelf")
|
|
test2.concretize()
|
|
test2.write()
|
|
|
|
env("create", "--include-concrete", "test2", "test3")
|
|
test3 = ev.read("test3")
|
|
|
|
with open(test3.lock_path) as f:
|
|
lockfile_as_dict = test3._read_lockfile(f)
|
|
|
|
assert test2.path in lockfile_as_dict["include_concrete"]
|
|
assert test1.path in lockfile_as_dict["include_concrete"][test2.path]["include_concrete"]
|
|
|
|
assert Spec("zlib") in test3.included_concretized_user_specs[test1.path]
|
|
|
|
|
|
def test_concretize_nested_included_concrete():
|
|
"""Confirm that nested included environments use specs concretized at
|
|
environment creation time and change with reconcretization."""
|
|
env("create", "test1")
|
|
test1 = ev.read("test1")
|
|
with test1:
|
|
add("zlib")
|
|
test1.concretize()
|
|
test1.write()
|
|
|
|
# test2 should include test1 with zlib
|
|
env("create", "--include-concrete", "test1", "test2")
|
|
test2 = ev.read("test2")
|
|
with test2:
|
|
add("libelf")
|
|
test2.concretize()
|
|
test2.write()
|
|
|
|
assert Spec("zlib") in test2.included_concretized_user_specs[test1.path]
|
|
|
|
# Modify/re-concretize test1 to replace zlib with mpileaks
|
|
with test1:
|
|
remove("zlib")
|
|
add("mpileaks")
|
|
test1.concretize()
|
|
test1.write()
|
|
|
|
# test3 should include the latest concretization of test1
|
|
env("create", "--include-concrete", "test1", "test3")
|
|
test3 = ev.read("test3")
|
|
with test3:
|
|
add("callpath")
|
|
test3.concretize()
|
|
test3.write()
|
|
|
|
included_specs = test3.included_concretized_user_specs[test1.path]
|
|
assert len(included_specs) == 1
|
|
assert Spec("mpileaks") in included_specs
|
|
|
|
# The last concretization of test4's included environments should have test2
|
|
# with the original concretized test1 spec and test3 with the re-concretized
|
|
# test1 spec.
|
|
env("create", "--include-concrete", "test2", "--include-concrete", "test3", "test4")
|
|
test4 = ev.read("test4")
|
|
|
|
def included_included_spec(path1, path2):
|
|
included_path1 = test4.included_concrete_spec_data[path1]
|
|
included_path2 = included_path1["include_concrete"][path2]
|
|
return included_path2["roots"][0]["spec"]
|
|
|
|
included_test2_test1 = included_included_spec(test2.path, test1.path)
|
|
assert "zlib" in included_test2_test1
|
|
|
|
included_test3_test1 = included_included_spec(test3.path, test1.path)
|
|
assert "mpileaks" in included_test3_test1
|
|
|
|
# test4's concretized specs should reflect the original concretization.
|
|
concrete_specs = [s for s, _ in test4.concretized_specs()]
|
|
expected = [Spec(s) for s in ["libelf", "zlib", "mpileaks", "callpath"]]
|
|
assert all(s in concrete_specs for s in expected)
|
|
|
|
# Re-concretize test2 to reflect the new concretization of included test1
|
|
# to remove zlib and write it out so it can be picked up by test4.
|
|
# Re-concretize test4 to reflect the re-concretization of included test2
|
|
# and ensure that its included specs are up-to-date
|
|
test2.concretize()
|
|
test2.write()
|
|
test4.concretize()
|
|
|
|
concrete_specs = [s for s, _ in test4.concretized_specs()]
|
|
assert Spec("zlib") not in concrete_specs
|
|
|
|
# Expecting mpileaks to appear only once
|
|
expected = [Spec(s) for s in ["libelf", "mpileaks", "callpath"]]
|
|
assert len(concrete_specs) == 3 and all(s in concrete_specs for s in expected)
|
|
|
|
|
|
def test_env_config_view_default(
|
|
environment_from_manifest, mock_stage, mock_fetch, install_mockery
|
|
):
|
|
# This config doesn't mention whether a view is enabled
|
|
environment_from_manifest(
|
|
"""
|
|
spack:
|
|
specs:
|
|
- mpileaks
|
|
"""
|
|
)
|
|
|
|
with ev.read("test"):
|
|
install("--fake")
|
|
|
|
e = ev.read("test")
|
|
|
|
# Check that metadata folder for this spec exists
|
|
assert os.path.isdir(os.path.join(e.default_view.view()._root, ".spack", "mpileaks"))
|
|
|
|
|
|
def test_env_updates_view_install_package(tmpdir, mock_stage, mock_fetch, install_mockery):
|
|
view_dir = tmpdir.join("view")
|
|
env("create", "--with-view=%s" % view_dir, "test")
|
|
with ev.read("test"):
|
|
install("--fake", "--add", "mpileaks")
|
|
|
|
assert os.path.exists(str(view_dir.join(".spack/mpileaks")))
|
|
|
|
|
|
def test_env_updates_view_add_concretize(tmpdir, mock_stage, mock_fetch, install_mockery):
|
|
view_dir = tmpdir.join("view")
|
|
env("create", "--with-view=%s" % view_dir, "test")
|
|
install("--fake", "mpileaks")
|
|
with ev.read("test"):
|
|
add("mpileaks")
|
|
concretize()
|
|
|
|
check_mpileaks_and_deps_in_view(view_dir)
|
|
|
|
|
|
def test_env_updates_view_uninstall(tmpdir, mock_stage, mock_fetch, install_mockery):
|
|
view_dir = tmpdir.join("view")
|
|
env("create", "--with-view=%s" % view_dir, "test")
|
|
with ev.read("test"):
|
|
install("--fake", "--add", "mpileaks")
|
|
|
|
check_mpileaks_and_deps_in_view(view_dir)
|
|
|
|
with ev.read("test"):
|
|
uninstall("-ay")
|
|
|
|
check_viewdir_removal(view_dir)
|
|
|
|
|
|
def test_env_updates_view_uninstall_referenced_elsewhere(
|
|
tmpdir, mock_stage, mock_fetch, install_mockery
|
|
):
|
|
view_dir = tmpdir.join("view")
|
|
env("create", "--with-view=%s" % view_dir, "test")
|
|
install("--fake", "mpileaks")
|
|
with ev.read("test"):
|
|
add("mpileaks")
|
|
concretize()
|
|
|
|
check_mpileaks_and_deps_in_view(view_dir)
|
|
|
|
with ev.read("test"):
|
|
uninstall("-ay")
|
|
|
|
check_viewdir_removal(view_dir)
|
|
|
|
|
|
def test_env_updates_view_remove_concretize(tmpdir, mock_stage, mock_fetch, install_mockery):
|
|
view_dir = tmpdir.join("view")
|
|
env("create", "--with-view=%s" % view_dir, "test")
|
|
install("--fake", "mpileaks")
|
|
with ev.read("test"):
|
|
add("mpileaks")
|
|
concretize()
|
|
|
|
check_mpileaks_and_deps_in_view(view_dir)
|
|
|
|
with ev.read("test"):
|
|
remove("mpileaks")
|
|
concretize()
|
|
|
|
check_viewdir_removal(view_dir)
|
|
|
|
|
|
def test_env_updates_view_force_remove(tmpdir, mock_stage, mock_fetch, install_mockery):
|
|
view_dir = tmpdir.join("view")
|
|
env("create", "--with-view=%s" % view_dir, "test")
|
|
with ev.read("test"):
|
|
install("--add", "--fake", "mpileaks")
|
|
|
|
check_mpileaks_and_deps_in_view(view_dir)
|
|
|
|
with ev.read("test"):
|
|
remove("-f", "mpileaks")
|
|
|
|
check_viewdir_removal(view_dir)
|
|
|
|
|
|
def test_env_activate_view_fails(tmpdir, mock_stage, mock_fetch, install_mockery):
|
|
"""Sanity check on env activate to make sure it requires shell support"""
|
|
out = env("activate", "test")
|
|
assert "To set up shell support" in out
|
|
|
|
|
|
def test_stack_yaml_definitions(tmpdir):
|
|
filename = str(tmpdir.join("spack.yaml"))
|
|
with open(filename, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
definitions:
|
|
- packages: [mpileaks, callpath]
|
|
specs:
|
|
- $packages
|
|
"""
|
|
)
|
|
with tmpdir.as_cwd():
|
|
env("create", "test", "./spack.yaml")
|
|
test = ev.read("test")
|
|
|
|
assert Spec("mpileaks") in test.user_specs
|
|
assert Spec("callpath") in test.user_specs
|
|
|
|
|
|
def test_stack_yaml_definitions_as_constraints(tmpdir):
|
|
filename = str(tmpdir.join("spack.yaml"))
|
|
with open(filename, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
definitions:
|
|
- packages: [mpileaks, callpath]
|
|
- mpis: [mpich, openmpi]
|
|
specs:
|
|
- matrix:
|
|
- [$packages]
|
|
- [$^mpis]
|
|
"""
|
|
)
|
|
with tmpdir.as_cwd():
|
|
env("create", "test", "./spack.yaml")
|
|
test = ev.read("test")
|
|
|
|
assert Spec("mpileaks^mpich") in test.user_specs
|
|
assert Spec("callpath^mpich") in test.user_specs
|
|
assert Spec("mpileaks^openmpi") in test.user_specs
|
|
assert Spec("callpath^openmpi") in test.user_specs
|
|
|
|
|
|
def test_stack_yaml_definitions_as_constraints_on_matrix(tmpdir):
|
|
filename = str(tmpdir.join("spack.yaml"))
|
|
with open(filename, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
definitions:
|
|
- packages: [mpileaks, callpath]
|
|
- mpis:
|
|
- matrix:
|
|
- [mpich]
|
|
- ['@3.0.4', '@3.0.3']
|
|
specs:
|
|
- matrix:
|
|
- [$packages]
|
|
- [$^mpis]
|
|
"""
|
|
)
|
|
with tmpdir.as_cwd():
|
|
env("create", "test", "./spack.yaml")
|
|
test = ev.read("test")
|
|
|
|
assert Spec("mpileaks^mpich@3.0.4") in test.user_specs
|
|
assert Spec("callpath^mpich@3.0.4") in test.user_specs
|
|
assert Spec("mpileaks^mpich@3.0.3") in test.user_specs
|
|
assert Spec("callpath^mpich@3.0.3") in test.user_specs
|
|
|
|
|
|
@pytest.mark.regression("12095")
|
|
def test_stack_yaml_definitions_write_reference(tmpdir):
|
|
filename = str(tmpdir.join("spack.yaml"))
|
|
with open(filename, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
definitions:
|
|
- packages: [mpileaks, callpath]
|
|
- indirect: [$packages]
|
|
specs:
|
|
- $packages
|
|
"""
|
|
)
|
|
with tmpdir.as_cwd():
|
|
env("create", "test", "./spack.yaml")
|
|
|
|
with ev.read("test"):
|
|
concretize()
|
|
test = ev.read("test")
|
|
|
|
assert Spec("mpileaks") in test.user_specs
|
|
assert Spec("callpath") in test.user_specs
|
|
|
|
|
|
def test_stack_yaml_add_to_list(tmpdir):
|
|
filename = str(tmpdir.join("spack.yaml"))
|
|
with open(filename, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
definitions:
|
|
- packages: [mpileaks, callpath]
|
|
specs:
|
|
- $packages
|
|
"""
|
|
)
|
|
with tmpdir.as_cwd():
|
|
env("create", "test", "./spack.yaml")
|
|
with ev.read("test"):
|
|
add("-l", "packages", "libelf")
|
|
|
|
test = ev.read("test")
|
|
|
|
assert Spec("libelf") in test.user_specs
|
|
assert Spec("mpileaks") in test.user_specs
|
|
assert Spec("callpath") in test.user_specs
|
|
|
|
|
|
def test_stack_yaml_remove_from_list(tmpdir):
|
|
filename = str(tmpdir.join("spack.yaml"))
|
|
with open(filename, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
definitions:
|
|
- packages: [mpileaks, callpath]
|
|
specs:
|
|
- $packages
|
|
"""
|
|
)
|
|
with tmpdir.as_cwd():
|
|
env("create", "test", "./spack.yaml")
|
|
with ev.read("test"):
|
|
remove("-l", "packages", "mpileaks")
|
|
|
|
test = ev.read("test")
|
|
|
|
assert Spec("mpileaks") not in test.user_specs
|
|
assert Spec("callpath") in test.user_specs
|
|
|
|
|
|
def test_stack_yaml_remove_from_list_force(tmp_path):
|
|
spack_yaml = tmp_path / ev.manifest_name
|
|
spack_yaml.write_text(
|
|
"""\
|
|
spack:
|
|
definitions:
|
|
- packages: [mpileaks, callpath]
|
|
specs:
|
|
- matrix:
|
|
- [$packages]
|
|
- [^mpich, ^zmpi]
|
|
"""
|
|
)
|
|
|
|
env("create", "test", str(spack_yaml))
|
|
with ev.read("test"):
|
|
concretize()
|
|
remove("-f", "-l", "packages", "mpileaks")
|
|
find_output = find("-c")
|
|
|
|
assert "mpileaks" not in find_output
|
|
|
|
test = ev.read("test")
|
|
assert len(test.user_specs) == 2
|
|
assert Spec("callpath ^zmpi") in test.user_specs
|
|
assert Spec("callpath ^mpich") in test.user_specs
|
|
|
|
|
|
def test_stack_yaml_remove_from_matrix_no_effect(tmpdir):
|
|
filename = str(tmpdir.join("spack.yaml"))
|
|
with open(filename, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
definitions:
|
|
- packages:
|
|
- matrix:
|
|
- [mpileaks, callpath]
|
|
- [target=be]
|
|
specs:
|
|
- $packages
|
|
"""
|
|
)
|
|
with tmpdir.as_cwd():
|
|
env("create", "test", "./spack.yaml")
|
|
with ev.read("test") as e:
|
|
before = e.user_specs.specs
|
|
remove("-l", "packages", "mpileaks")
|
|
after = e.user_specs.specs
|
|
|
|
assert before == after
|
|
|
|
|
|
def test_stack_yaml_force_remove_from_matrix(tmpdir):
|
|
filename = str(tmpdir.join("spack.yaml"))
|
|
with open(filename, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
definitions:
|
|
- packages:
|
|
- matrix:
|
|
- [mpileaks, callpath]
|
|
- [target=be]
|
|
specs:
|
|
- $packages
|
|
"""
|
|
)
|
|
with tmpdir.as_cwd():
|
|
env("create", "test", "./spack.yaml")
|
|
with ev.read("test") as e:
|
|
e.concretize()
|
|
|
|
before_user = e.user_specs.specs
|
|
before_conc = e.concretized_user_specs
|
|
|
|
remove("-f", "-l", "packages", "mpileaks")
|
|
|
|
after_user = e.user_specs.specs
|
|
after_conc = e.concretized_user_specs
|
|
|
|
assert before_user == after_user
|
|
|
|
mpileaks_spec = Spec("mpileaks target=be")
|
|
assert mpileaks_spec in before_conc
|
|
assert mpileaks_spec not in after_conc
|
|
|
|
|
|
def test_stack_definition_extension(tmpdir):
|
|
filename = str(tmpdir.join("spack.yaml"))
|
|
with open(filename, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
definitions:
|
|
- packages: [libelf, mpileaks]
|
|
- packages: [callpath]
|
|
specs:
|
|
- $packages
|
|
"""
|
|
)
|
|
with tmpdir.as_cwd():
|
|
env("create", "test", "./spack.yaml")
|
|
|
|
test = ev.read("test")
|
|
|
|
assert Spec("libelf") in test.user_specs
|
|
assert Spec("mpileaks") in test.user_specs
|
|
assert Spec("callpath") in test.user_specs
|
|
|
|
|
|
def test_stack_definition_conditional_false(tmpdir):
|
|
filename = str(tmpdir.join("spack.yaml"))
|
|
with open(filename, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
definitions:
|
|
- packages: [libelf, mpileaks]
|
|
- packages: [callpath]
|
|
when: 'False'
|
|
specs:
|
|
- $packages
|
|
"""
|
|
)
|
|
with tmpdir.as_cwd():
|
|
env("create", "test", "./spack.yaml")
|
|
|
|
test = ev.read("test")
|
|
|
|
assert Spec("libelf") in test.user_specs
|
|
assert Spec("mpileaks") in test.user_specs
|
|
assert Spec("callpath") not in test.user_specs
|
|
|
|
|
|
def test_stack_definition_conditional_true(tmpdir):
|
|
filename = str(tmpdir.join("spack.yaml"))
|
|
with open(filename, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
definitions:
|
|
- packages: [libelf, mpileaks]
|
|
- packages: [callpath]
|
|
when: 'True'
|
|
specs:
|
|
- $packages
|
|
"""
|
|
)
|
|
with tmpdir.as_cwd():
|
|
env("create", "test", "./spack.yaml")
|
|
|
|
test = ev.read("test")
|
|
|
|
assert Spec("libelf") in test.user_specs
|
|
assert Spec("mpileaks") in test.user_specs
|
|
assert Spec("callpath") in test.user_specs
|
|
|
|
|
|
def test_stack_definition_conditional_with_variable(tmpdir):
|
|
filename = str(tmpdir.join("spack.yaml"))
|
|
with open(filename, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
definitions:
|
|
- packages: [libelf, mpileaks]
|
|
- packages: [callpath]
|
|
when: platform == 'test'
|
|
specs:
|
|
- $packages
|
|
"""
|
|
)
|
|
with tmpdir.as_cwd():
|
|
env("create", "test", "./spack.yaml")
|
|
|
|
test = ev.read("test")
|
|
|
|
assert Spec("libelf") in test.user_specs
|
|
assert Spec("mpileaks") in test.user_specs
|
|
assert Spec("callpath") in test.user_specs
|
|
|
|
|
|
def test_stack_definition_conditional_with_satisfaction(tmpdir):
|
|
filename = str(tmpdir.join("spack.yaml"))
|
|
with open(filename, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
definitions:
|
|
- packages: [libelf, mpileaks]
|
|
when: arch.satisfies('platform=foo') # will be "test" when testing
|
|
- packages: [callpath]
|
|
when: arch.satisfies('platform=test')
|
|
specs:
|
|
- $packages
|
|
"""
|
|
)
|
|
with tmpdir.as_cwd():
|
|
env("create", "test", "./spack.yaml")
|
|
|
|
test = ev.read("test")
|
|
|
|
assert Spec("libelf") not in test.user_specs
|
|
assert Spec("mpileaks") not in test.user_specs
|
|
assert Spec("callpath") in test.user_specs
|
|
|
|
|
|
def test_stack_definition_complex_conditional(tmpdir):
|
|
filename = str(tmpdir.join("spack.yaml"))
|
|
with open(filename, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
definitions:
|
|
- packages: [libelf, mpileaks]
|
|
- packages: [callpath]
|
|
when: re.search(r'foo', hostname) and env['test'] == 'THISSHOULDBEFALSE'
|
|
specs:
|
|
- $packages
|
|
"""
|
|
)
|
|
with tmpdir.as_cwd():
|
|
env("create", "test", "./spack.yaml")
|
|
|
|
test = ev.read("test")
|
|
|
|
assert Spec("libelf") in test.user_specs
|
|
assert Spec("mpileaks") in test.user_specs
|
|
assert Spec("callpath") not in test.user_specs
|
|
|
|
|
|
def test_stack_definition_conditional_invalid_variable(tmpdir):
|
|
filename = str(tmpdir.join("spack.yaml"))
|
|
with open(filename, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
definitions:
|
|
- packages: [libelf, mpileaks]
|
|
- packages: [callpath]
|
|
when: bad_variable == 'test'
|
|
specs:
|
|
- $packages
|
|
"""
|
|
)
|
|
with tmpdir.as_cwd():
|
|
with pytest.raises(NameError):
|
|
env("create", "test", "./spack.yaml")
|
|
|
|
|
|
def test_stack_definition_conditional_add_write(tmpdir):
|
|
filename = str(tmpdir.join("spack.yaml"))
|
|
with open(filename, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
definitions:
|
|
- packages: [libelf, mpileaks]
|
|
- packages: [callpath]
|
|
when: platform == 'test'
|
|
specs:
|
|
- $packages
|
|
"""
|
|
)
|
|
with tmpdir.as_cwd():
|
|
env("create", "test", "./spack.yaml")
|
|
with ev.read("test"):
|
|
add("-l", "packages", "zmpi")
|
|
|
|
test = ev.read("test")
|
|
|
|
packages_lists = list(
|
|
filter(lambda x: "packages" in x, test.manifest["spack"]["definitions"])
|
|
)
|
|
|
|
assert len(packages_lists) == 2
|
|
assert "callpath" not in packages_lists[0]["packages"]
|
|
assert "callpath" in packages_lists[1]["packages"]
|
|
assert "zmpi" in packages_lists[0]["packages"]
|
|
assert "zmpi" not in packages_lists[1]["packages"]
|
|
|
|
|
|
def test_stack_combinatorial_view(
|
|
tmpdir, mock_fetch, mock_packages, mock_archive, install_mockery
|
|
):
|
|
filename = str(tmpdir.join("spack.yaml"))
|
|
viewdir = str(tmpdir.join("view"))
|
|
with open(filename, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
definitions:
|
|
- packages: [mpileaks, callpath]
|
|
- compilers: ['%%gcc', '%%clang']
|
|
specs:
|
|
- matrix:
|
|
- [$packages]
|
|
- [$compilers]
|
|
|
|
view:
|
|
combinatorial:
|
|
root: %s
|
|
projections:
|
|
'all': '{name}/{version}-{compiler.name}'"""
|
|
% viewdir
|
|
)
|
|
with tmpdir.as_cwd():
|
|
env("create", "test", "./spack.yaml")
|
|
with ev.read("test"):
|
|
install()
|
|
|
|
test = ev.read("test")
|
|
for spec in test._get_environment_specs():
|
|
assert os.path.exists(
|
|
os.path.join(viewdir, spec.name, "%s-%s" % (spec.version, spec.compiler.name))
|
|
)
|
|
|
|
|
|
def test_stack_view_select(tmpdir, mock_fetch, mock_packages, mock_archive, install_mockery):
|
|
filename = str(tmpdir.join("spack.yaml"))
|
|
viewdir = str(tmpdir.join("view"))
|
|
with open(filename, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
definitions:
|
|
- packages: [mpileaks, callpath]
|
|
- compilers: ['%%gcc', '%%clang']
|
|
specs:
|
|
- matrix:
|
|
- [$packages]
|
|
- [$compilers]
|
|
|
|
view:
|
|
combinatorial:
|
|
root: %s
|
|
select: ['%%gcc']
|
|
projections:
|
|
'all': '{name}/{version}-{compiler.name}'"""
|
|
% viewdir
|
|
)
|
|
with tmpdir.as_cwd():
|
|
env("create", "test", "./spack.yaml")
|
|
with ev.read("test"):
|
|
install()
|
|
|
|
test = ev.read("test")
|
|
for spec in test._get_environment_specs():
|
|
if spec.satisfies("%gcc"):
|
|
assert os.path.exists(
|
|
os.path.join(viewdir, spec.name, "%s-%s" % (spec.version, spec.compiler.name))
|
|
)
|
|
else:
|
|
assert not os.path.exists(
|
|
os.path.join(viewdir, spec.name, "%s-%s" % (spec.version, spec.compiler.name))
|
|
)
|
|
|
|
|
|
def test_stack_view_exclude(tmpdir, mock_fetch, mock_packages, mock_archive, install_mockery):
|
|
filename = str(tmpdir.join("spack.yaml"))
|
|
viewdir = str(tmpdir.join("view"))
|
|
with open(filename, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
definitions:
|
|
- packages: [mpileaks, callpath]
|
|
- compilers: ['%%gcc', '%%clang']
|
|
specs:
|
|
- matrix:
|
|
- [$packages]
|
|
- [$compilers]
|
|
|
|
view:
|
|
combinatorial:
|
|
root: %s
|
|
exclude: [callpath]
|
|
projections:
|
|
'all': '{name}/{version}-{compiler.name}'"""
|
|
% viewdir
|
|
)
|
|
with tmpdir.as_cwd():
|
|
env("create", "test", "./spack.yaml")
|
|
with ev.read("test"):
|
|
install()
|
|
|
|
test = ev.read("test")
|
|
for spec in test._get_environment_specs():
|
|
if not spec.satisfies("callpath"):
|
|
assert os.path.exists(
|
|
os.path.join(viewdir, spec.name, "%s-%s" % (spec.version, spec.compiler.name))
|
|
)
|
|
else:
|
|
assert not os.path.exists(
|
|
os.path.join(viewdir, spec.name, "%s-%s" % (spec.version, spec.compiler.name))
|
|
)
|
|
|
|
|
|
def test_stack_view_select_and_exclude(
|
|
tmpdir, mock_fetch, mock_packages, mock_archive, install_mockery
|
|
):
|
|
filename = str(tmpdir.join("spack.yaml"))
|
|
viewdir = str(tmpdir.join("view"))
|
|
with open(filename, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
definitions:
|
|
- packages: [mpileaks, callpath]
|
|
- compilers: ['%%gcc', '%%clang']
|
|
specs:
|
|
- matrix:
|
|
- [$packages]
|
|
- [$compilers]
|
|
|
|
view:
|
|
combinatorial:
|
|
root: %s
|
|
select: ['%%gcc']
|
|
exclude: [callpath]
|
|
projections:
|
|
'all': '{name}/{version}-{compiler.name}'"""
|
|
% viewdir
|
|
)
|
|
with tmpdir.as_cwd():
|
|
env("create", "test", "./spack.yaml")
|
|
with ev.read("test"):
|
|
install()
|
|
|
|
test = ev.read("test")
|
|
for spec in test._get_environment_specs():
|
|
if spec.satisfies("%gcc") and not spec.satisfies("callpath"):
|
|
assert os.path.exists(
|
|
os.path.join(viewdir, spec.name, "%s-%s" % (spec.version, spec.compiler.name))
|
|
)
|
|
else:
|
|
assert not os.path.exists(
|
|
os.path.join(viewdir, spec.name, "%s-%s" % (spec.version, spec.compiler.name))
|
|
)
|
|
|
|
|
|
def test_view_link_roots(tmpdir, mock_fetch, mock_packages, mock_archive, install_mockery):
|
|
filename = str(tmpdir.join("spack.yaml"))
|
|
viewdir = str(tmpdir.join("view"))
|
|
with open(filename, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
definitions:
|
|
- packages: [mpileaks, callpath]
|
|
- compilers: ['%%gcc', '%%clang']
|
|
specs:
|
|
- matrix:
|
|
- [$packages]
|
|
- [$compilers]
|
|
|
|
view:
|
|
combinatorial:
|
|
root: %s
|
|
select: ['%%gcc']
|
|
exclude: [callpath]
|
|
link: 'roots'
|
|
projections:
|
|
'all': '{name}/{version}-{compiler.name}'"""
|
|
% viewdir
|
|
)
|
|
with tmpdir.as_cwd():
|
|
env("create", "test", "./spack.yaml")
|
|
with ev.read("test"):
|
|
install()
|
|
|
|
test = ev.read("test")
|
|
for spec in test._get_environment_specs():
|
|
if spec in test.roots() and (
|
|
spec.satisfies("%gcc") and not spec.satisfies("callpath")
|
|
):
|
|
assert os.path.exists(
|
|
os.path.join(viewdir, spec.name, "%s-%s" % (spec.version, spec.compiler.name))
|
|
)
|
|
else:
|
|
assert not os.path.exists(
|
|
os.path.join(viewdir, spec.name, "%s-%s" % (spec.version, spec.compiler.name))
|
|
)
|
|
|
|
|
|
def test_view_link_run(tmpdir, mock_fetch, mock_packages, mock_archive, install_mockery):
|
|
yaml = str(tmpdir.join("spack.yaml"))
|
|
viewdir = str(tmpdir.join("view"))
|
|
envdir = str(tmpdir)
|
|
with open(yaml, "w") as f:
|
|
f.write(
|
|
"""
|
|
spack:
|
|
specs:
|
|
- dttop
|
|
|
|
view:
|
|
combinatorial:
|
|
root: %s
|
|
link: run
|
|
projections:
|
|
all: '{name}'"""
|
|
% viewdir
|
|
)
|
|
|
|
with ev.Environment(envdir):
|
|
install()
|
|
|
|
# make sure transitive run type deps are in the view
|
|
for pkg in ("dtrun1", "dtrun3"):
|
|
assert os.path.exists(os.path.join(viewdir, pkg))
|
|
|
|
# and non-run-type deps are not.
|
|
for pkg in (
|
|
"dtlink1",
|
|
"dtlink2",
|
|
"dtlink3",
|
|
"dtlink4",
|
|
"dtlink5" "dtbuild1",
|
|
"dtbuild2",
|
|
"dtbuild3",
|
|
):
|
|
assert not os.path.exists(os.path.join(viewdir, pkg))
|
|
|
|
|
|
@pytest.mark.parametrize("link_type", ["hardlink", "copy", "symlink"])
|
|
def test_view_link_type(
|
|
link_type, tmpdir, mock_fetch, mock_packages, mock_archive, install_mockery
|
|
):
|
|
filename = str(tmpdir.join("spack.yaml"))
|
|
viewdir = str(tmpdir.join("view"))
|
|
with open(filename, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
specs:
|
|
- mpileaks
|
|
view:
|
|
default:
|
|
root: %s
|
|
link_type: %s"""
|
|
% (viewdir, link_type)
|
|
)
|
|
with tmpdir.as_cwd():
|
|
env("create", "test", "./spack.yaml")
|
|
with ev.read("test"):
|
|
install()
|
|
|
|
test = ev.read("test")
|
|
|
|
for spec in test.roots():
|
|
file_path = test.default_view.view()._root
|
|
file_to_test = os.path.join(file_path, spec.name)
|
|
assert os.path.isfile(file_to_test)
|
|
assert os.path.islink(file_to_test) == (link_type == "symlink")
|
|
|
|
|
|
def test_view_link_all(tmpdir, mock_fetch, mock_packages, mock_archive, install_mockery):
|
|
filename = str(tmpdir.join("spack.yaml"))
|
|
viewdir = str(tmpdir.join("view"))
|
|
with open(filename, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
definitions:
|
|
- packages: [mpileaks, callpath]
|
|
- compilers: ['%%gcc', '%%clang']
|
|
specs:
|
|
- matrix:
|
|
- [$packages]
|
|
- [$compilers]
|
|
|
|
view:
|
|
combinatorial:
|
|
root: %s
|
|
select: ['%%gcc']
|
|
exclude: [callpath]
|
|
link: 'all'
|
|
projections:
|
|
'all': '{name}/{version}-{compiler.name}'"""
|
|
% viewdir
|
|
)
|
|
with tmpdir.as_cwd():
|
|
env("create", "test", "./spack.yaml")
|
|
with ev.read("test"):
|
|
install()
|
|
|
|
test = ev.read("test")
|
|
for spec in test._get_environment_specs():
|
|
if spec.satisfies("%gcc") and not spec.satisfies("callpath"):
|
|
assert os.path.exists(
|
|
os.path.join(viewdir, spec.name, "%s-%s" % (spec.version, spec.compiler.name))
|
|
)
|
|
else:
|
|
assert not os.path.exists(
|
|
os.path.join(viewdir, spec.name, "%s-%s" % (spec.version, spec.compiler.name))
|
|
)
|
|
|
|
|
|
def test_stack_view_activate_from_default(
|
|
tmpdir, mock_fetch, mock_packages, mock_archive, install_mockery
|
|
):
|
|
filename = str(tmpdir.join("spack.yaml"))
|
|
viewdir = str(tmpdir.join("view"))
|
|
with open(filename, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
definitions:
|
|
- packages: [mpileaks, cmake]
|
|
- compilers: ['%%gcc', '%%clang']
|
|
specs:
|
|
- matrix:
|
|
- [$packages]
|
|
- [$compilers]
|
|
|
|
view:
|
|
default:
|
|
root: %s
|
|
select: ['%%gcc']"""
|
|
% viewdir
|
|
)
|
|
with tmpdir.as_cwd():
|
|
env("create", "test", "./spack.yaml")
|
|
with ev.read("test"):
|
|
install()
|
|
|
|
shell = env("activate", "--sh", "test")
|
|
|
|
assert "PATH" in shell
|
|
assert os.path.join(viewdir, "bin") in shell
|
|
assert "FOOBAR=mpileaks" in shell
|
|
|
|
|
|
def test_stack_view_no_activate_without_default(
|
|
tmpdir, mock_fetch, mock_packages, mock_archive, install_mockery
|
|
):
|
|
filename = str(tmpdir.join("spack.yaml"))
|
|
viewdir = str(tmpdir.join("view"))
|
|
with open(filename, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
definitions:
|
|
- packages: [mpileaks, cmake]
|
|
- compilers: ['%%gcc', '%%clang']
|
|
specs:
|
|
- matrix:
|
|
- [$packages]
|
|
- [$compilers]
|
|
|
|
view:
|
|
not-default:
|
|
root: %s
|
|
select: ['%%gcc']"""
|
|
% viewdir
|
|
)
|
|
with tmpdir.as_cwd():
|
|
env("create", "test", "./spack.yaml")
|
|
with ev.read("test"):
|
|
install()
|
|
|
|
shell = env("activate", "--sh", "test")
|
|
assert "PATH" not in shell
|
|
assert viewdir not in shell
|
|
|
|
|
|
@pytest.mark.parametrize("include_views", [True, False, "split"])
|
|
def test_stack_view_multiple_views(
|
|
tmp_path,
|
|
mock_fetch,
|
|
mock_packages,
|
|
mock_archive,
|
|
install_mockery,
|
|
mutable_config,
|
|
include_views,
|
|
):
|
|
"""Test multiple views as both included views (True), as both environment
|
|
views (False), or as one included and the other in the environment."""
|
|
# Write the view configuration and or manifest file
|
|
view_filename = tmp_path / "view.yaml"
|
|
base_content = """\
|
|
definitions:
|
|
- packages: [mpileaks, cmake]
|
|
- compilers: ['%gcc', '%clang']
|
|
specs:
|
|
- matrix:
|
|
- [$packages]
|
|
- [$compilers]
|
|
"""
|
|
|
|
include_content = f" include:\n - {view_filename}\n"
|
|
view_line = " view:\n"
|
|
|
|
comb_dir = tmp_path / "combinatorial-view"
|
|
comb_view = """\
|
|
{0}combinatorial:
|
|
{0} root: {1}
|
|
{0} exclude: [callpath%gcc]
|
|
{0} projections:
|
|
"""
|
|
|
|
projection = " 'all': '{name}/{version}-{compiler.name}'"
|
|
|
|
default_dir = tmp_path / "default-view"
|
|
default_view = """\
|
|
{0}default:
|
|
{0} root: {1}
|
|
{0} select: ['%gcc']
|
|
"""
|
|
|
|
content = "spack:\n"
|
|
indent = " "
|
|
if include_views is True:
|
|
# Include both the gcc and combinatorial views
|
|
view = "view:\n" + default_view.format(indent, str(default_dir))
|
|
view += comb_view.format(indent, str(comb_dir)) + indent + projection
|
|
view_filename.write_text(view)
|
|
content += include_content + base_content
|
|
elif include_views == "split":
|
|
# Include the gcc view and inline the combinatorial view
|
|
view = "view:\n" + default_view.format(indent, str(default_dir))
|
|
view_filename.write_text(view)
|
|
content += include_content + base_content + view_line
|
|
indent += " "
|
|
content += comb_view.format(indent, str(comb_dir)) + indent + projection
|
|
else:
|
|
# Inline both the gcc and combinatorial views in the environment.
|
|
indent += " "
|
|
content += base_content + view_line
|
|
content += default_view.format(indent, str(default_dir))
|
|
content += comb_view.format(indent, str(comb_dir)) + indent + projection
|
|
|
|
filename = tmp_path / ev.manifest_name
|
|
filename.write_text(content)
|
|
|
|
env("create", "test", str(filename))
|
|
with ev.read("test"):
|
|
install()
|
|
|
|
with ev.read("test") as e:
|
|
assert os.path.exists(str(default_dir / "bin"))
|
|
for spec in e._get_environment_specs():
|
|
spec_subdir = f"{spec.version}-{spec.compiler.name}"
|
|
comb_spec_dir = str(comb_dir / spec.name / spec_subdir)
|
|
if not spec.satisfies("callpath%gcc"):
|
|
assert os.path.exists(comb_spec_dir)
|
|
else:
|
|
assert not os.path.exists(comb_spec_dir)
|
|
|
|
|
|
def test_env_activate_sh_prints_shell_output(tmpdir, mock_stage, mock_fetch, install_mockery):
|
|
"""Check the shell commands output by ``spack env activate --sh``.
|
|
|
|
This is a cursory check; ``share/spack/qa/setup-env-test.sh`` checks
|
|
for correctness.
|
|
"""
|
|
env("create", "test")
|
|
|
|
out = env("activate", "--sh", "test")
|
|
assert "export SPACK_ENV=" in out
|
|
assert "export PS1=" not in out
|
|
assert "alias despacktivate=" in out
|
|
|
|
out = env("activate", "--sh", "--prompt", "test")
|
|
assert "export SPACK_ENV=" in out
|
|
assert "export PS1=" in out
|
|
assert "alias despacktivate=" in out
|
|
|
|
|
|
def test_env_activate_csh_prints_shell_output(tmpdir, mock_stage, mock_fetch, install_mockery):
|
|
"""Check the shell commands output by ``spack env activate --csh``."""
|
|
env("create", "test")
|
|
|
|
out = env("activate", "--csh", "test")
|
|
assert "setenv SPACK_ENV" in out
|
|
assert "setenv set prompt" not in out
|
|
assert "alias despacktivate" in out
|
|
|
|
out = env("activate", "--csh", "--prompt", "test")
|
|
assert "setenv SPACK_ENV" in out
|
|
assert "set prompt=" in out
|
|
assert "alias despacktivate" in out
|
|
|
|
|
|
@pytest.mark.regression("12719")
|
|
def test_env_activate_default_view_root_unconditional(mutable_mock_env_path):
|
|
"""Check that the root of the default view in the environment is added
|
|
to the shell unconditionally."""
|
|
env("create", "test")
|
|
|
|
with ev.read("test") as e:
|
|
viewdir = e.default_view.root
|
|
|
|
out = env("activate", "--sh", "test")
|
|
viewdir_bin = os.path.join(viewdir, "bin")
|
|
|
|
assert (
|
|
"export PATH={0}".format(viewdir_bin) in out
|
|
or "export PATH='{0}".format(viewdir_bin) in out
|
|
or 'export PATH="{0}'.format(viewdir_bin) in out
|
|
)
|
|
|
|
|
|
def test_env_activate_custom_view(tmp_path: pathlib.Path, mock_packages):
|
|
"""Check that an environment can be activated with a non-default view."""
|
|
env_template = tmp_path / "spack.yaml"
|
|
default_dir = tmp_path / "defaultdir"
|
|
nondefaultdir = tmp_path / "nondefaultdir"
|
|
with open(env_template, "w") as f:
|
|
f.write(
|
|
f"""\
|
|
spack:
|
|
specs: [a]
|
|
view:
|
|
default:
|
|
root: {default_dir}
|
|
nondefault:
|
|
root: {nondefaultdir}"""
|
|
)
|
|
env("create", "test", str(env_template))
|
|
shell = env("activate", "--sh", "--with-view", "nondefault", "test")
|
|
assert os.path.join(nondefaultdir, "bin") in shell
|
|
|
|
|
|
def test_concretize_user_specs_together():
|
|
e = ev.create("coconcretization")
|
|
e.unify = True
|
|
|
|
# Concretize a first time using 'mpich' as the MPI provider
|
|
e.add("mpileaks")
|
|
e.add("mpich")
|
|
e.concretize()
|
|
|
|
assert all("mpich" in spec for _, spec in e.concretized_specs())
|
|
assert all("mpich2" not in spec for _, spec in e.concretized_specs())
|
|
|
|
# Concretize a second time using 'mpich2' as the MPI provider
|
|
e.remove("mpich")
|
|
e.add("mpich2")
|
|
|
|
exc_cls = spack.error.UnsatisfiableSpecError
|
|
|
|
# Concretizing without invalidating the concrete spec for mpileaks fails
|
|
with pytest.raises(exc_cls):
|
|
e.concretize()
|
|
e.concretize(force=True)
|
|
|
|
assert all("mpich2" in spec for _, spec in e.concretized_specs())
|
|
assert all("mpich" not in spec for _, spec in e.concretized_specs())
|
|
|
|
# Concretize again without changing anything, check everything
|
|
# stays the same
|
|
e.concretize()
|
|
|
|
assert all("mpich2" in spec for _, spec in e.concretized_specs())
|
|
assert all("mpich" not in spec for _, spec in e.concretized_specs())
|
|
|
|
|
|
def test_duplicate_packages_raise_when_concretizing_together():
|
|
e = ev.create("coconcretization")
|
|
e.unify = True
|
|
|
|
e.add("mpileaks+opt")
|
|
e.add("mpileaks~opt")
|
|
e.add("mpich")
|
|
|
|
exc_cls = spack.error.UnsatisfiableSpecError
|
|
match = r"You could consider setting `concretizer:unify`"
|
|
|
|
with pytest.raises(exc_cls, match=match):
|
|
e.concretize()
|
|
|
|
|
|
def test_env_write_only_non_default():
|
|
env("create", "test")
|
|
|
|
e = ev.read("test")
|
|
with open(e.manifest_path, "r") as f:
|
|
yaml = f.read()
|
|
|
|
assert yaml == ev.default_manifest_yaml()
|
|
|
|
|
|
@pytest.mark.regression("20526")
|
|
def test_env_write_only_non_default_nested(tmpdir):
|
|
# setup an environment file
|
|
# the environment includes configuration because nested configs proved the
|
|
# most difficult to avoid writing.
|
|
filename = "spack.yaml"
|
|
filepath = str(tmpdir.join(filename))
|
|
contents = """\
|
|
spack:
|
|
specs:
|
|
- matrix:
|
|
- [mpileaks]
|
|
packages:
|
|
all:
|
|
compiler: [gcc]
|
|
view: true
|
|
"""
|
|
|
|
# create environment with some structure
|
|
with open(filepath, "w") as f:
|
|
f.write(contents)
|
|
env("create", "test", filepath)
|
|
|
|
# concretize
|
|
with ev.read("test") as e:
|
|
concretize()
|
|
e.write()
|
|
|
|
with open(e.manifest_path, "r") as f:
|
|
manifest = f.read()
|
|
|
|
assert manifest == contents
|
|
|
|
|
|
@pytest.mark.regression("18147")
|
|
def test_can_update_attributes_with_override(tmpdir):
|
|
spack_yaml = """
|
|
spack:
|
|
mirrors::
|
|
test: /foo/bar
|
|
packages:
|
|
cmake:
|
|
paths:
|
|
cmake@3.18.1: /usr
|
|
specs:
|
|
- hdf5
|
|
"""
|
|
abspath = tmpdir.join("spack.yaml")
|
|
abspath.write(spack_yaml)
|
|
|
|
# Check that an update does not raise
|
|
env("update", "-y", str(abspath.dirname))
|
|
|
|
|
|
@pytest.mark.regression("18338")
|
|
def test_newline_in_commented_sequence_is_not_an_issue(tmpdir):
|
|
spack_yaml = """
|
|
spack:
|
|
specs:
|
|
- dyninst
|
|
packages:
|
|
libelf:
|
|
externals:
|
|
- spec: libelf@0.8.13
|
|
modules:
|
|
- libelf/3.18.1
|
|
|
|
concretizer:
|
|
unify: false
|
|
"""
|
|
abspath = tmpdir.join("spack.yaml")
|
|
abspath.write(spack_yaml)
|
|
|
|
def extract_dag_hash(environment):
|
|
_, dyninst = next(iter(environment.specs_by_hash.items()))
|
|
return dyninst["libelf"].dag_hash()
|
|
|
|
# Concretize a first time and create a lockfile
|
|
with ev.Environment(str(tmpdir)) as e:
|
|
concretize()
|
|
libelf_first_hash = extract_dag_hash(e)
|
|
|
|
# Check that a second run won't error
|
|
with ev.Environment(str(tmpdir)) as e:
|
|
concretize()
|
|
libelf_second_hash = extract_dag_hash(e)
|
|
|
|
assert libelf_first_hash == libelf_second_hash
|
|
|
|
|
|
@pytest.mark.regression("18441")
|
|
def test_lockfile_not_deleted_on_write_error(tmpdir, monkeypatch):
|
|
raw_yaml = """
|
|
spack:
|
|
specs:
|
|
- dyninst
|
|
packages:
|
|
libelf:
|
|
externals:
|
|
- spec: libelf@0.8.13
|
|
prefix: /usr
|
|
"""
|
|
spack_yaml = tmpdir.join("spack.yaml")
|
|
spack_yaml.write(raw_yaml)
|
|
spack_lock = tmpdir.join("spack.lock")
|
|
|
|
# Concretize a first time and create a lockfile
|
|
with ev.Environment(str(tmpdir)):
|
|
concretize()
|
|
assert os.path.exists(str(spack_lock))
|
|
|
|
# If I run concretize again and there's an error during write,
|
|
# the spack.lock file shouldn't disappear from disk
|
|
def _write_helper_raise(self):
|
|
raise RuntimeError("some error")
|
|
|
|
monkeypatch.setattr(
|
|
spack.environment.environment.EnvironmentManifestFile, "flush", _write_helper_raise
|
|
)
|
|
with ev.Environment(str(tmpdir)) as e:
|
|
e.concretize(force=True)
|
|
with pytest.raises(RuntimeError):
|
|
e.clear()
|
|
e.write()
|
|
assert os.path.exists(str(spack_lock))
|
|
|
|
|
|
def _setup_develop_packages(tmpdir):
|
|
"""Sets up a structure ./init_env/spack.yaml, ./build_folder, ./dest_env
|
|
where spack.yaml has a relative develop path to build_folder"""
|
|
init_env = tmpdir.join("init_env")
|
|
build_folder = tmpdir.join("build_folder")
|
|
dest_env = tmpdir.join("dest_env")
|
|
|
|
fs.mkdirp(str(init_env))
|
|
fs.mkdirp(str(build_folder))
|
|
fs.mkdirp(str(dest_env))
|
|
|
|
raw_yaml = """
|
|
spack:
|
|
specs: ['mypkg1', 'mypkg2']
|
|
develop:
|
|
mypkg1:
|
|
path: ../build_folder
|
|
spec: mypkg@main
|
|
mypkg2:
|
|
path: /some/other/path
|
|
spec: mypkg@main
|
|
"""
|
|
spack_yaml = init_env.join("spack.yaml")
|
|
spack_yaml.write(raw_yaml)
|
|
|
|
return init_env, build_folder, dest_env, spack_yaml
|
|
|
|
|
|
def test_rewrite_rel_dev_path_new_dir(tmpdir):
|
|
"""Relative develop paths should be rewritten for new environments in
|
|
a different directory from the original manifest file"""
|
|
_, build_folder, dest_env, spack_yaml = _setup_develop_packages(tmpdir)
|
|
|
|
env("create", "-d", str(dest_env), str(spack_yaml))
|
|
with ev.Environment(str(dest_env)) as e:
|
|
assert e.dev_specs["mypkg1"]["path"] == str(build_folder)
|
|
assert e.dev_specs["mypkg2"]["path"] == sep + os.path.join("some", "other", "path")
|
|
|
|
|
|
def test_rewrite_rel_dev_path_named_env(tmpdir):
|
|
"""Relative develop paths should by default be rewritten for new named
|
|
environment"""
|
|
_, build_folder, _, spack_yaml = _setup_develop_packages(tmpdir)
|
|
env("create", "named_env", str(spack_yaml))
|
|
with ev.read("named_env") as e:
|
|
assert e.dev_specs["mypkg1"]["path"] == str(build_folder)
|
|
assert e.dev_specs["mypkg2"]["path"] == sep + os.path.join("some", "other", "path")
|
|
|
|
|
|
def test_does_not_rewrite_rel_dev_path_when_keep_relative_is_set(tmpdir):
|
|
"""Relative develop paths should not be rewritten when --keep-relative is
|
|
passed to create"""
|
|
_, _, _, spack_yaml = _setup_develop_packages(tmpdir)
|
|
env("create", "--keep-relative", "named_env", str(spack_yaml))
|
|
with ev.read("named_env") as e:
|
|
assert e.dev_specs["mypkg1"]["path"] == "../build_folder"
|
|
assert e.dev_specs["mypkg2"]["path"] == "/some/other/path"
|
|
|
|
|
|
@pytest.mark.regression("23440")
|
|
def test_custom_version_concretize_together(tmpdir):
|
|
# Custom versions should be permitted in specs when
|
|
# concretizing together
|
|
e = ev.create("custom_version")
|
|
e.unify = True
|
|
|
|
# Concretize a first time using 'mpich' as the MPI provider
|
|
e.add("hdf5@=myversion")
|
|
e.add("mpich")
|
|
e.concretize()
|
|
|
|
assert any(spec.satisfies("hdf5@myversion") for _, spec in e.concretized_specs())
|
|
|
|
|
|
def test_modules_relative_to_views(environment_from_manifest, install_mockery, mock_fetch):
|
|
environment_from_manifest(
|
|
"""
|
|
spack:
|
|
specs:
|
|
- trivial-install-test-package
|
|
modules:
|
|
default:
|
|
enable:: [tcl]
|
|
use_view: true
|
|
roots:
|
|
tcl: modules
|
|
"""
|
|
)
|
|
|
|
with ev.read("test") as e:
|
|
install()
|
|
|
|
spec = e.specs_by_hash[e.concretized_order[0]]
|
|
view_prefix = e.default_view.get_projection_for_spec(spec)
|
|
modules_glob = "%s/modules/**/*/*" % e.path
|
|
modules = glob.glob(modules_glob)
|
|
assert len(modules) == 1
|
|
module = modules[0]
|
|
|
|
with open(module, "r") as f:
|
|
contents = f.read()
|
|
|
|
assert view_prefix in contents
|
|
assert spec.prefix not in contents
|
|
|
|
|
|
def test_modules_exist_after_env_install(
|
|
environment_from_manifest, install_mockery, mock_fetch, monkeypatch
|
|
):
|
|
# Some caching issue
|
|
monkeypatch.setattr(spack.modules.tcl, "configuration_registry", {})
|
|
environment_from_manifest(
|
|
"""
|
|
spack:
|
|
specs:
|
|
- mpileaks
|
|
modules:
|
|
default:
|
|
enable:: [tcl]
|
|
use_view: true
|
|
roots:
|
|
tcl: uses_view
|
|
full:
|
|
enable:: [tcl]
|
|
roots:
|
|
tcl: without_view
|
|
"""
|
|
)
|
|
|
|
with ev.read("test") as e:
|
|
install()
|
|
specs = e.all_specs()
|
|
|
|
for module_set in ("uses_view", "without_view"):
|
|
modules = glob.glob(f"{e.path}/{module_set}/**/*/*")
|
|
assert len(modules) == len(specs), "Not all modules were generated"
|
|
for spec in specs:
|
|
module = next((m for m in modules if os.path.dirname(m).endswith(spec.name)), None)
|
|
assert module, f"Module for {spec} not found"
|
|
|
|
# Now verify that modules have paths pointing into the view instead of the package
|
|
# prefix if and only if they set use_view to true.
|
|
with open(module, "r") as f:
|
|
contents = f.read()
|
|
|
|
if module_set == "uses_view":
|
|
assert e.default_view.get_projection_for_spec(spec) in contents
|
|
assert spec.prefix not in contents
|
|
else:
|
|
assert e.default_view.get_projection_for_spec(spec) not in contents
|
|
assert spec.prefix in contents
|
|
|
|
|
|
@pytest.mark.disable_clean_stage_check
|
|
def test_install_develop_keep_stage(
|
|
environment_from_manifest, install_mockery, mock_fetch, monkeypatch, tmpdir
|
|
):
|
|
"""Develop a dependency of a package and make sure that the associated
|
|
stage for the package is retained after a successful install.
|
|
"""
|
|
environment_from_manifest(
|
|
"""
|
|
spack:
|
|
specs:
|
|
- mpileaks
|
|
"""
|
|
)
|
|
|
|
monkeypatch.setattr(spack.stage.DevelopStage, "destroy", _always_fail)
|
|
|
|
with ev.read("test") as e:
|
|
libelf_dev_path = tmpdir.ensure("libelf-test-dev-path", dir=True)
|
|
develop(f"--path={libelf_dev_path}", "libelf@0.8.13")
|
|
concretize()
|
|
(libelf_spec,) = e.all_matching_specs("libelf")
|
|
(mpileaks_spec,) = e.all_matching_specs("mpileaks")
|
|
assert not os.path.exists(libelf_spec.package.stage.path)
|
|
assert not os.path.exists(mpileaks_spec.package.stage.path)
|
|
install()
|
|
assert os.path.exists(libelf_spec.package.stage.path)
|
|
assert not os.path.exists(mpileaks_spec.package.stage.path)
|
|
|
|
|
|
# Helper method for test_install_develop_keep_stage
|
|
def _always_fail(cls, *args, **kwargs):
|
|
raise Exception("Restage or destruction of dev stage detected during install")
|
|
|
|
|
|
@pytest.mark.regression("24148")
|
|
def test_virtual_spec_concretize_together(tmpdir):
|
|
# An environment should permit to concretize "mpi"
|
|
e = ev.create("virtual_spec")
|
|
e.unify = True
|
|
|
|
e.add("mpi")
|
|
e.concretize()
|
|
|
|
assert any(s.package.provides("mpi") for _, s in e.concretized_specs())
|
|
|
|
|
|
def test_query_develop_specs(tmpdir):
|
|
"""Test whether a spec is develop'ed or not"""
|
|
srcdir = tmpdir.ensure("here")
|
|
|
|
env("create", "test")
|
|
with ev.read("test") as e:
|
|
e.add("mpich")
|
|
e.add("mpileaks")
|
|
develop("--no-clone", "-p", str(srcdir), "mpich@=1")
|
|
|
|
assert e.is_develop(Spec("mpich"))
|
|
assert not e.is_develop(Spec("mpileaks"))
|
|
|
|
|
|
@pytest.mark.parametrize("method", [spack.cmd.env.env_activate, spack.cmd.env.env_deactivate])
|
|
@pytest.mark.parametrize(
|
|
"env,no_env,env_dir", [("b", False, None), (None, True, None), (None, False, "path/")]
|
|
)
|
|
def test_activation_and_deactiviation_ambiguities(method, env, no_env, env_dir, capsys):
|
|
"""spack [-e x | -E | -D x/] env [activate | deactivate] y are ambiguous"""
|
|
args = Namespace(
|
|
shell="sh", env_name="a", env=env, no_env=no_env, env_dir=env_dir, keep_relative=False
|
|
)
|
|
with pytest.raises(SystemExit):
|
|
method(args)
|
|
_, err = capsys.readouterr()
|
|
assert "is ambiguous" in err
|
|
|
|
|
|
@pytest.mark.regression("26548")
|
|
def test_custom_store_in_environment(mutable_config, tmpdir):
|
|
spack_yaml = tmpdir.join("spack.yaml")
|
|
install_root = tmpdir.join("store")
|
|
spack_yaml.write(
|
|
"""
|
|
spack:
|
|
specs:
|
|
- libelf
|
|
config:
|
|
install_tree:
|
|
root: {0}
|
|
""".format(
|
|
install_root
|
|
)
|
|
)
|
|
current_store_root = str(spack.store.STORE.root)
|
|
assert str(current_store_root) != install_root
|
|
with spack.environment.Environment(str(tmpdir)):
|
|
assert str(spack.store.STORE.root) == install_root
|
|
assert str(spack.store.STORE.root) == current_store_root
|
|
|
|
|
|
def test_activate_temp(monkeypatch, tmpdir):
|
|
"""Tests whether `spack env activate --temp` creates an environment in a
|
|
temporary directory"""
|
|
env_dir = lambda: str(tmpdir)
|
|
monkeypatch.setattr(spack.cmd.env, "create_temp_env_directory", env_dir)
|
|
shell = env("activate", "--temp", "--sh")
|
|
active_env_var = next(line for line in shell.splitlines() if ev.spack_env_var in line)
|
|
assert str(tmpdir) in active_env_var
|
|
assert ev.is_env_dir(str(tmpdir))
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"conflict_arg", [["--dir"], ["--keep-relative"], ["--with-view", "foo"], ["env"]]
|
|
)
|
|
def test_activate_parser_conflicts_with_temp(conflict_arg):
|
|
with pytest.raises(SpackCommandError):
|
|
env("activate", "--sh", "--temp", *conflict_arg)
|
|
|
|
|
|
def test_create_and_activate_managed(tmp_path):
|
|
with fs.working_dir(str(tmp_path)):
|
|
shell = env("activate", "--without-view", "--create", "--sh", "foo")
|
|
active_env_var = next(line for line in shell.splitlines() if ev.spack_env_var in line)
|
|
assert str(tmp_path) in active_env_var
|
|
active_ev = ev.active_environment()
|
|
assert "foo" == active_ev.name
|
|
env("deactivate")
|
|
|
|
|
|
def test_create_and_activate_anonymous(tmp_path):
|
|
with fs.working_dir(str(tmp_path)):
|
|
env_dir = os.path.join(str(tmp_path), "foo")
|
|
shell = env("activate", "--without-view", "--create", "--sh", env_dir)
|
|
active_env_var = next(line for line in shell.splitlines() if ev.spack_env_var in line)
|
|
assert str(env_dir) in active_env_var
|
|
assert ev.is_env_dir(env_dir)
|
|
env("deactivate")
|
|
|
|
|
|
def test_activate_default(monkeypatch):
|
|
"""Tests whether `spack env activate` creates / activates the default
|
|
environment"""
|
|
assert not ev.exists("default")
|
|
|
|
# Activating it the first time should create it
|
|
env("activate", "--sh")
|
|
env("deactivate", "--sh")
|
|
assert ev.exists("default")
|
|
|
|
# Activating it while it already exists should work
|
|
env("activate", "--sh")
|
|
env("deactivate", "--sh")
|
|
assert ev.exists("default")
|
|
|
|
env("remove", "-y", "default")
|
|
assert not ev.exists("default")
|
|
|
|
|
|
def test_env_view_fail_if_symlink_points_elsewhere(tmpdir, install_mockery, mock_fetch):
|
|
view = str(tmpdir.join("view"))
|
|
# Put a symlink to an actual directory in view
|
|
non_view_dir = str(tmpdir.mkdir("dont-delete-me"))
|
|
os.symlink(non_view_dir, view)
|
|
with ev.create("env", with_view=view):
|
|
add("libelf")
|
|
install("--fake")
|
|
assert os.path.isdir(non_view_dir)
|
|
|
|
|
|
def test_failed_view_cleanup(tmp_path, mock_stage, mock_fetch, install_mockery):
|
|
"""Tests whether Spack cleans up after itself when a view fails to create"""
|
|
view_dir = tmp_path / "view"
|
|
with ev.create("env", with_view=str(view_dir)):
|
|
add("libelf")
|
|
install("--fake")
|
|
|
|
# Save the current view directory.
|
|
resolved_view = view_dir.resolve(strict=True)
|
|
all_views = resolved_view.parent
|
|
views_before = list(all_views.iterdir())
|
|
|
|
# Add a spec that results in view clash when creating a view
|
|
with ev.read("env"):
|
|
add("libelf cflags=-O3")
|
|
with pytest.raises(ev.SpackEnvironmentViewError):
|
|
install("--fake")
|
|
|
|
# Make sure there is no broken view in the views directory, and the current
|
|
# view is the original view from before the failed regenerate attempt.
|
|
views_after = list(all_views.iterdir())
|
|
assert views_before == views_after
|
|
assert view_dir.samefile(resolved_view), view_dir
|
|
|
|
|
|
def test_environment_view_target_already_exists(tmpdir, mock_stage, mock_fetch, install_mockery):
|
|
"""When creating a new view, Spack should check whether
|
|
the new view dir already exists. If so, it should not be
|
|
removed or modified."""
|
|
|
|
# Create a new environment
|
|
view = str(tmpdir.join("view"))
|
|
env("create", "--with-view={0}".format(view), "test")
|
|
with ev.read("test"):
|
|
add("libelf")
|
|
install("--fake")
|
|
|
|
# Empty the underlying view
|
|
real_view = os.path.realpath(view)
|
|
assert os.listdir(real_view) # make sure it had *some* contents
|
|
shutil.rmtree(real_view)
|
|
|
|
# Replace it with something new.
|
|
os.mkdir(real_view)
|
|
fs.touch(os.path.join(real_view, "file"))
|
|
|
|
# Remove the symlink so Spack can't know about the "previous root"
|
|
os.unlink(view)
|
|
|
|
# Regenerate the view, which should realize it can't write into the same dir.
|
|
msg = "Failed to generate environment view"
|
|
with ev.read("test"):
|
|
with pytest.raises(ev.SpackEnvironmentViewError, match=msg):
|
|
env("view", "regenerate")
|
|
|
|
# Make sure the dir was left untouched.
|
|
assert not os.path.lexists(view)
|
|
assert os.listdir(real_view) == ["file"]
|
|
|
|
|
|
def test_environment_query_spec_by_hash(mock_stage, mock_fetch, install_mockery):
|
|
env("create", "test")
|
|
with ev.read("test"):
|
|
add("libdwarf")
|
|
concretize()
|
|
with ev.read("test") as e:
|
|
spec = e.matching_spec("libelf")
|
|
install("/{0}".format(spec.dag_hash()))
|
|
with ev.read("test") as e:
|
|
assert not e.matching_spec("libdwarf").installed
|
|
assert e.matching_spec("libelf").installed
|
|
|
|
|
|
@pytest.mark.parametrize("lockfile", ["v1", "v2", "v3"])
|
|
def test_read_old_lock_and_write_new(tmpdir, lockfile):
|
|
# v1 lockfiles stored by a coarse DAG hash that did not include build deps.
|
|
# They could not represent multiple build deps with different build hashes.
|
|
#
|
|
# v2 and v3 lockfiles are keyed by a "build hash", so they can represent specs
|
|
# with different build deps but the same DAG hash. However, those two specs
|
|
# could never have been built together, because they cannot coexist in a
|
|
# Spack DB, which is keyed by DAG hash. The second one would just be a no-op
|
|
# no-op because its DAG hash was already in the DB.
|
|
#
|
|
# Newer Spack uses a fine-grained DAG hash that includes build deps, package hash,
|
|
# and more. But, we still have to identify old specs by their original DAG hash.
|
|
# Essentially, the name (hash) we give something in Spack at concretization time is
|
|
# its name forever (otherwise we'd need to relocate prefixes and disrupt existing
|
|
# installations). So, we just discard the second conflicting dtbuild1 version when
|
|
# reading v2 and v3 lockfiles. This is what old Spack would've done when installing
|
|
# the environment, anyway.
|
|
#
|
|
# This test ensures the behavior described above.
|
|
lockfile_path = os.path.join(spack.paths.test_path, "data", "legacy_env", "%s.lock" % lockfile)
|
|
|
|
# read in the JSON from a legacy lockfile
|
|
with open(lockfile_path) as f:
|
|
old_dict = sjson.load(f)
|
|
|
|
# read all DAG hashes from the legacy lockfile and record its shadowed DAG hash.
|
|
old_hashes = set()
|
|
shadowed_hash = None
|
|
for key, spec_dict in old_dict["concrete_specs"].items():
|
|
if "hash" not in spec_dict:
|
|
# v1 and v2 key specs by their name in concrete_specs
|
|
name, spec_dict = next(iter(spec_dict.items()))
|
|
else:
|
|
# v3 lockfiles have a `name` field and key by hash
|
|
name = spec_dict["name"]
|
|
|
|
# v1 lockfiles do not have a "hash" field -- they use the key.
|
|
dag_hash = key if lockfile == "v1" else spec_dict["hash"]
|
|
old_hashes.add(dag_hash)
|
|
|
|
# v1 lockfiles can't store duplicate build dependencies, so they
|
|
# will not have a shadowed hash.
|
|
if lockfile != "v1":
|
|
# v2 and v3 lockfiles store specs by build hash, so they can have multiple
|
|
# keys for the same DAG hash. We discard the second one (dtbuild@1.0).
|
|
if name == "dtbuild1" and spec_dict["version"] == "1.0":
|
|
shadowed_hash = dag_hash
|
|
|
|
# make an env out of the old lockfile -- env should be able to read v1/v2/v3
|
|
test_lockfile_path = str(tmpdir.join("spack.lock"))
|
|
shutil.copy(lockfile_path, test_lockfile_path)
|
|
_env_create("test", init_file=test_lockfile_path, with_view=False)
|
|
|
|
# re-read the old env as a new lockfile
|
|
e = ev.read("test")
|
|
hashes = set(e._to_lockfile_dict()["concrete_specs"])
|
|
|
|
# v1 doesn't have duplicate build deps.
|
|
# in v2 and v3, the shadowed hash will be gone.
|
|
if shadowed_hash:
|
|
old_hashes -= set([shadowed_hash])
|
|
|
|
# make sure we see the same hashes in old and new lockfiles
|
|
assert old_hashes == hashes
|
|
|
|
|
|
def test_read_v1_lock_creates_backup(tmp_path):
|
|
"""When reading a version-1 lockfile, make sure that a backup of that file
|
|
is created.
|
|
"""
|
|
v1_lockfile_path = pathlib.Path(spack.paths.test_path) / "data" / "legacy_env" / "v1.lock"
|
|
test_lockfile_path = tmp_path / "init" / ev.lockfile_name
|
|
test_lockfile_path.parent.mkdir(parents=True, exist_ok=False)
|
|
shutil.copy(v1_lockfile_path, test_lockfile_path)
|
|
|
|
e = ev.create_in_dir(tmp_path, init_file=test_lockfile_path)
|
|
assert os.path.exists(e._lock_backup_v1_path)
|
|
assert filecmp.cmp(e._lock_backup_v1_path, v1_lockfile_path)
|
|
|
|
|
|
@pytest.mark.parametrize("lockfile", ["v1", "v2", "v3"])
|
|
def test_read_legacy_lockfile_and_reconcretize(
|
|
mock_stage, mock_fetch, install_mockery, lockfile, tmp_path
|
|
):
|
|
# In legacy lockfiles v2 and v3 (keyed by build hash), there may be multiple
|
|
# versions of the same spec with different build dependencies, which means
|
|
# they will have different build hashes but the same DAG hash.
|
|
# In the case of DAG hash conflicts, we always keep the spec associated with
|
|
# whichever root spec came first in the "roots" list.
|
|
#
|
|
# After reconcretization with the *new*, finer-grained DAG hash, there should no
|
|
# longer be conflicts, and the previously conflicting specs can coexist in the
|
|
# same environment.
|
|
test_path = pathlib.Path(spack.paths.test_path)
|
|
lockfile_content = test_path / "data" / "legacy_env" / f"{lockfile}.lock"
|
|
legacy_lockfile_path = tmp_path / ev.lockfile_name
|
|
shutil.copy(lockfile_content, legacy_lockfile_path)
|
|
|
|
# The order of the root specs in this environment is:
|
|
# [
|
|
# wci7a3a -> dttop ^dtbuild1@0.5,
|
|
# 5zg6wxw -> dttop ^dtbuild1@1.0
|
|
# ]
|
|
# So in v2 and v3 lockfiles we have two versions of dttop with the same DAG
|
|
# hash but different build hashes.
|
|
|
|
env("create", "test", str(legacy_lockfile_path))
|
|
test = ev.read("test")
|
|
assert len(test.specs_by_hash) == 1
|
|
|
|
single_root = next(iter(test.specs_by_hash.values()))
|
|
|
|
# v1 only has version 1.0, because v1 was keyed by DAG hash, and v1.0 overwrote
|
|
# v0.5 on lockfile creation. v2 only has v0.5, because we specifically prefer
|
|
# the one that would be installed when we read old lockfiles.
|
|
if lockfile == "v1":
|
|
assert single_root["dtbuild1"].version == Version("1.0")
|
|
else:
|
|
assert single_root["dtbuild1"].version == Version("0.5")
|
|
|
|
# Now forcefully reconcretize
|
|
with ev.read("test"):
|
|
concretize("-f")
|
|
|
|
# After reconcretizing, we should again see two roots, one depending on each
|
|
# of the dtbuild1 versions specified in the roots of the original lockfile.
|
|
test = ev.read("test")
|
|
assert len(test.specs_by_hash) == 2
|
|
|
|
expected_versions = set([Version("0.5"), Version("1.0")])
|
|
current_versions = set(s["dtbuild1"].version for s in test.specs_by_hash.values())
|
|
assert current_versions == expected_versions
|
|
|
|
|
|
def _parse_dry_run_package_installs(make_output):
|
|
"""Parse `spack install ... # <spec>` output from a make dry run."""
|
|
return [
|
|
Spec(line.split("# ")[1]).name
|
|
for line in make_output.splitlines()
|
|
if line.startswith("spack")
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"depfile_flags,expected_installs",
|
|
[
|
|
# This installs the full environment
|
|
(
|
|
["--use-buildcache=never"],
|
|
[
|
|
"dtbuild1",
|
|
"dtbuild2",
|
|
"dtbuild3",
|
|
"dtlink1",
|
|
"dtlink2",
|
|
"dtlink3",
|
|
"dtlink4",
|
|
"dtlink5",
|
|
"dtrun1",
|
|
"dtrun2",
|
|
"dtrun3",
|
|
"dttop",
|
|
],
|
|
),
|
|
# This prunes build deps at depth > 0
|
|
(
|
|
["--use-buildcache=package:never,dependencies:only"],
|
|
[
|
|
"dtbuild1",
|
|
"dtlink1",
|
|
"dtlink2",
|
|
"dtlink3",
|
|
"dtlink4",
|
|
"dtlink5",
|
|
"dtrun1",
|
|
"dtrun2",
|
|
"dtrun3",
|
|
"dttop",
|
|
],
|
|
),
|
|
# This prunes all build deps
|
|
(
|
|
["--use-buildcache=only"],
|
|
["dtlink1", "dtlink3", "dtlink4", "dtlink5", "dtrun1", "dtrun3", "dttop"],
|
|
),
|
|
# Test whether pruning of build deps is correct if we explicitly include one
|
|
# that is also a dependency of a root.
|
|
(
|
|
["--use-buildcache=only", "dttop", "dtbuild1"],
|
|
[
|
|
"dtbuild1",
|
|
"dtlink1",
|
|
"dtlink2",
|
|
"dtlink3",
|
|
"dtlink4",
|
|
"dtlink5",
|
|
"dtrun1",
|
|
"dtrun2",
|
|
"dtrun3",
|
|
"dttop",
|
|
],
|
|
),
|
|
],
|
|
)
|
|
def test_environment_depfile_makefile(depfile_flags, expected_installs, tmpdir, mock_packages):
|
|
env("create", "test")
|
|
make = Executable("make")
|
|
makefile = str(tmpdir.join("Makefile"))
|
|
with ev.read("test"):
|
|
add("dttop")
|
|
concretize()
|
|
|
|
# Disable jobserver so we can do a dry run.
|
|
with ev.read("test"):
|
|
env(
|
|
"depfile",
|
|
"-o",
|
|
makefile,
|
|
"--make-disable-jobserver",
|
|
"--make-prefix=prefix",
|
|
*depfile_flags,
|
|
)
|
|
|
|
# Do make dry run.
|
|
out = make("-n", "-f", makefile, output=str)
|
|
|
|
specs_that_make_would_install = _parse_dry_run_package_installs(out)
|
|
|
|
# Check that all specs are there (without duplicates)
|
|
assert set(specs_that_make_would_install) == set(expected_installs)
|
|
assert len(specs_that_make_would_install) == len(set(specs_that_make_would_install))
|
|
|
|
|
|
def test_depfile_safe_format():
|
|
"""Test that depfile.MakefileSpec.safe_format() escapes target names."""
|
|
|
|
class SpecLike:
|
|
def format(self, _):
|
|
return "abc@def=ghi"
|
|
|
|
spec = depfile.MakefileSpec(SpecLike())
|
|
assert spec.safe_format("{name}") == "abc_def_ghi"
|
|
assert spec.unsafe_format("{name}") == "abc@def=ghi"
|
|
|
|
|
|
def test_depfile_works_with_gitversions(tmpdir, mock_packages, monkeypatch):
|
|
"""Git versions may contain = chars, which should be escaped in targets,
|
|
otherwise they're interpreted as makefile variable assignments."""
|
|
monkeypatch.setattr(spack.package_base.PackageBase, "git", "repo.git", raising=False)
|
|
env("create", "test")
|
|
|
|
make = Executable("make")
|
|
makefile = str(tmpdir.join("Makefile"))
|
|
|
|
# Create an environment with dttop and dtlink1 both at a git version,
|
|
# and generate a depfile
|
|
with ev.read("test"):
|
|
add(f"dttop@{'a' * 40}=1.0 ^dtlink1@{'b' * 40}=1.0")
|
|
concretize()
|
|
env("depfile", "-o", makefile, "--make-disable-jobserver", "--make-prefix=prefix")
|
|
|
|
# Do a dry run on the generated depfile
|
|
out = make("-n", "-f", makefile, output=str)
|
|
|
|
# Check that all specs are there (without duplicates)
|
|
specs_that_make_would_install = _parse_dry_run_package_installs(out)
|
|
expected_installs = [
|
|
"dtbuild1",
|
|
"dtbuild2",
|
|
"dtbuild3",
|
|
"dtlink1",
|
|
"dtlink2",
|
|
"dtlink3",
|
|
"dtlink4",
|
|
"dtlink5",
|
|
"dtrun1",
|
|
"dtrun2",
|
|
"dtrun3",
|
|
"dttop",
|
|
]
|
|
assert set(specs_that_make_would_install) == set(expected_installs)
|
|
assert len(specs_that_make_would_install) == len(set(specs_that_make_would_install))
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"picked_package,expected_installs",
|
|
[
|
|
(
|
|
"dttop",
|
|
[
|
|
"dtbuild2",
|
|
"dtlink2",
|
|
"dtrun2",
|
|
"dtbuild1",
|
|
"dtlink4",
|
|
"dtlink3",
|
|
"dtlink1",
|
|
"dtlink5",
|
|
"dtbuild3",
|
|
"dtrun3",
|
|
"dtrun1",
|
|
"dttop",
|
|
],
|
|
),
|
|
("dtrun1", ["dtlink5", "dtbuild3", "dtrun3", "dtrun1"]),
|
|
],
|
|
)
|
|
def test_depfile_phony_convenience_targets(
|
|
picked_package, expected_installs: set, tmpdir, mock_packages
|
|
):
|
|
"""Check whether convenience targets "install/%" and "install-deps/%" are created for
|
|
each package if "--make-prefix" is absent."""
|
|
make = Executable("make")
|
|
with fs.working_dir(str(tmpdir)):
|
|
with ev.create_in_dir("."):
|
|
add("dttop")
|
|
concretize()
|
|
|
|
with ev.Environment(".") as e:
|
|
picked_spec = e.matching_spec(picked_package)
|
|
env("depfile", "-o", "Makefile", "--make-disable-jobserver")
|
|
|
|
# Phony install/* target should install picked package and all its deps
|
|
specs_that_make_would_install = _parse_dry_run_package_installs(
|
|
make("-n", picked_spec.format("install/{name}-{version}-{hash}"), output=str)
|
|
)
|
|
|
|
assert set(specs_that_make_would_install) == set(expected_installs)
|
|
assert len(specs_that_make_would_install) == len(set(specs_that_make_would_install))
|
|
|
|
# Phony install-deps/* target shouldn't install picked package
|
|
specs_that_make_would_install = _parse_dry_run_package_installs(
|
|
make("-n", picked_spec.format("install-deps/{name}-{version}-{hash}"), output=str)
|
|
)
|
|
|
|
assert set(specs_that_make_would_install) == set(expected_installs) - {picked_package}
|
|
assert len(specs_that_make_would_install) == len(set(specs_that_make_would_install))
|
|
|
|
|
|
def test_environment_depfile_out(tmpdir, mock_packages):
|
|
env("create", "test")
|
|
makefile_path = str(tmpdir.join("Makefile"))
|
|
with ev.read("test"):
|
|
add("libdwarf")
|
|
concretize()
|
|
with ev.read("test"):
|
|
env("depfile", "-G", "make", "-o", makefile_path)
|
|
stdout = env("depfile", "-G", "make")
|
|
with open(makefile_path, "r") as f:
|
|
assert stdout == f.read()
|
|
|
|
|
|
def test_spack_package_ids_variable(tmpdir, mock_packages):
|
|
# Integration test for post-install hooks through prefix/SPACK_PACKAGE_IDS
|
|
# variable
|
|
env("create", "test")
|
|
makefile_path = str(tmpdir.join("Makefile"))
|
|
include_path = str(tmpdir.join("include.mk"))
|
|
|
|
# Create env and generate depfile in include.mk with prefix example/
|
|
with ev.read("test"):
|
|
add("libdwarf")
|
|
concretize()
|
|
|
|
with ev.read("test"):
|
|
env(
|
|
"depfile",
|
|
"-G",
|
|
"make",
|
|
"--make-disable-jobserver",
|
|
"--make-prefix=example",
|
|
"-o",
|
|
include_path,
|
|
)
|
|
|
|
# Include in Makefile and create target that depend on SPACK_PACKAGE_IDS
|
|
with open(makefile_path, "w") as f:
|
|
f.write(
|
|
r"""
|
|
all: post-install
|
|
|
|
include include.mk
|
|
|
|
example/post-install/%: example/install/%
|
|
$(info post-install: $(HASH)) # noqa: W191,E101
|
|
|
|
post-install: $(addprefix example/post-install/,$(example/SPACK_PACKAGE_IDS))
|
|
"""
|
|
)
|
|
make = Executable("make")
|
|
|
|
# Do dry run.
|
|
out = make("-n", "-C", str(tmpdir), output=str)
|
|
|
|
# post-install: <hash> should've been executed
|
|
with ev.read("test") as test:
|
|
for s in test.all_specs():
|
|
assert "post-install: {}".format(s.dag_hash()) in out
|
|
|
|
|
|
def test_depfile_empty_does_not_error(tmp_path):
|
|
# For empty environments Spack should create a depfile that does nothing
|
|
make = Executable("make")
|
|
makefile = str(tmp_path / "Makefile")
|
|
|
|
env("create", "test")
|
|
with ev.read("test"):
|
|
env("depfile", "-o", makefile)
|
|
|
|
make("-f", makefile)
|
|
|
|
assert make.returncode == 0
|
|
|
|
|
|
def test_unify_when_possible_works_around_conflicts():
|
|
e = ev.create("coconcretization")
|
|
e.unify = "when_possible"
|
|
|
|
e.add("mpileaks+opt")
|
|
e.add("mpileaks~opt")
|
|
e.add("mpich")
|
|
|
|
e.concretize()
|
|
|
|
assert len([x for x in e.all_specs() if x.satisfies("mpileaks")]) == 2
|
|
assert len([x for x in e.all_specs() if x.satisfies("mpileaks+opt")]) == 1
|
|
assert len([x for x in e.all_specs() if x.satisfies("mpileaks~opt")]) == 1
|
|
assert len([x for x in e.all_specs() if x.satisfies("mpich")]) == 1
|
|
|
|
|
|
def test_env_include_packages_url(
|
|
tmpdir, mutable_empty_config, mock_spider_configs, mock_curl_configs
|
|
):
|
|
"""Test inclusion of a (GitHub) URL."""
|
|
develop_url = "https://github.com/fake/fake/blob/develop/"
|
|
default_packages = develop_url + "etc/fake/defaults/packages.yaml"
|
|
spack_yaml = tmpdir.join("spack.yaml")
|
|
with spack_yaml.open("w") as f:
|
|
f.write("spack:\n include:\n - {0}\n".format(default_packages))
|
|
assert os.path.isfile(spack_yaml.strpath)
|
|
|
|
with spack.config.override("config:url_fetch_method", "curl"):
|
|
env = ev.Environment(tmpdir.strpath)
|
|
ev.activate(env)
|
|
|
|
cfg = spack.config.get("packages")
|
|
assert "openmpi" in cfg["all"]["providers"]["mpi"]
|
|
|
|
|
|
def test_relative_view_path_on_command_line_is_made_absolute(tmp_path):
|
|
with fs.working_dir(str(tmp_path)):
|
|
env("create", "--with-view", "view", "--dir", "env")
|
|
environment = ev.Environment(os.path.join(".", "env"))
|
|
environment.regenerate_views()
|
|
assert os.path.samefile("view", environment.default_view.root)
|
|
|
|
|
|
def test_environment_created_in_users_location(mutable_mock_env_path, tmp_path):
|
|
"""Test that an environment is created in a location based on the config"""
|
|
env_dir = str(mutable_mock_env_path)
|
|
|
|
assert str(tmp_path) in env_dir
|
|
assert not os.path.isdir(env_dir)
|
|
|
|
dir_name = "user_env"
|
|
env("create", dir_name)
|
|
out = env("list")
|
|
|
|
assert dir_name in out
|
|
assert env_dir in ev.root(dir_name)
|
|
assert os.path.isdir(os.path.join(env_dir, dir_name))
|
|
|
|
|
|
def test_environment_created_from_lockfile_has_view(mock_packages, temporary_store, tmpdir):
|
|
"""When an env is created from a lockfile, a view should be generated for it"""
|
|
env_a = str(tmpdir.join("a"))
|
|
env_b = str(tmpdir.join("b"))
|
|
|
|
# Create an environment and install a package in it
|
|
env("create", "-d", env_a)
|
|
with ev.Environment(env_a):
|
|
add("libelf")
|
|
install("--fake")
|
|
|
|
# Create another environment from the lockfile of the first environment
|
|
env("create", "-d", env_b, os.path.join(env_a, "spack.lock"))
|
|
|
|
# Make sure the view was created
|
|
with ev.Environment(env_b) as e:
|
|
assert os.path.isdir(e.view_path_default)
|
|
|
|
|
|
def test_env_view_disabled(tmp_path, mutable_mock_env_path):
|
|
"""Ensure an inlined view being disabled means not even the default view
|
|
is created (since the case doesn't appear to be covered in this module)."""
|
|
spack_yaml = tmp_path / ev.manifest_name
|
|
spack_yaml.write_text(
|
|
"""\
|
|
spack:
|
|
specs:
|
|
- mpileaks
|
|
view: false
|
|
"""
|
|
)
|
|
env("create", "disabled", str(spack_yaml))
|
|
with ev.read("disabled") as e:
|
|
e.concretize()
|
|
|
|
assert len(e.views) == 0
|
|
assert not os.path.exists(e.view_path_default)
|
|
|
|
|
|
@pytest.mark.parametrize("first", ["false", "true", "custom"])
|
|
def test_env_include_mixed_views(tmp_path, mutable_mock_env_path, mutable_config, first):
|
|
"""Ensure including path and boolean views in different combinations result
|
|
in the creation of only the first view if it is not disabled."""
|
|
false_yaml = tmp_path / "false-view.yaml"
|
|
false_yaml.write_text("view: false\n")
|
|
|
|
true_yaml = tmp_path / "true-view.yaml"
|
|
true_yaml.write_text("view: true\n")
|
|
|
|
custom_name = "my-test-view"
|
|
custom_view = tmp_path / custom_name
|
|
custom_yaml = tmp_path / "custom-view.yaml"
|
|
custom_yaml.write_text(
|
|
f"""
|
|
view:
|
|
{custom_name}:
|
|
root: {custom_view}
|
|
"""
|
|
)
|
|
|
|
if first == "false":
|
|
order = [false_yaml, true_yaml, custom_yaml]
|
|
elif first == "true":
|
|
order = [true_yaml, custom_yaml, false_yaml]
|
|
else:
|
|
order = [custom_yaml, false_yaml, true_yaml]
|
|
includes = [f" - {yaml}\n" for yaml in order]
|
|
|
|
spack_yaml = tmp_path / ev.manifest_name
|
|
spack_yaml.write_text(
|
|
f"""\
|
|
spack:
|
|
include:
|
|
{''.join(includes)}
|
|
specs:
|
|
- mpileaks
|
|
"""
|
|
)
|
|
|
|
env("create", "test", str(spack_yaml))
|
|
with ev.read("test") as e:
|
|
concretize()
|
|
|
|
# Only the first included view should be created if view not disabled by it
|
|
assert len(e.views) == 0 if first == "false" else 1
|
|
if first == "true":
|
|
assert os.path.exists(e.view_path_default)
|
|
else:
|
|
assert not os.path.exists(e.view_path_default)
|
|
|
|
if first == "custom":
|
|
assert os.path.exists(custom_view)
|
|
else:
|
|
assert not os.path.exists(custom_view)
|
|
|
|
|
|
def test_stack_view_multiple_views_same_name(
|
|
tmp_path, mock_fetch, mock_packages, mock_archive, install_mockery, mutable_config
|
|
):
|
|
"""Test multiple views with the same name combine settings with precedence
|
|
given to the options in spack.yaml."""
|
|
# Write the view configuration and or manifest file
|
|
|
|
view_filename = tmp_path / "view.yaml"
|
|
default_dir = tmp_path / "default-view"
|
|
default_view = f"""\
|
|
view:
|
|
default:
|
|
root: {default_dir}
|
|
select: ['%gcc']
|
|
projections:
|
|
all: '{{name}}/{{version}}-{{compiler.name}}'
|
|
"""
|
|
view_filename.write_text(default_view)
|
|
|
|
view_dir = tmp_path / "view"
|
|
content = f"""\
|
|
spack:
|
|
include:
|
|
- {view_filename}
|
|
definitions:
|
|
- packages: [mpileaks, cmake]
|
|
- compilers: ['%gcc', '%clang']
|
|
specs:
|
|
- matrix:
|
|
- [$packages]
|
|
- [$compilers]
|
|
|
|
view:
|
|
default:
|
|
root: {view_dir}
|
|
exclude: ['cmake']
|
|
projections:
|
|
all: '{{name}}/{{compiler.name}}-{{version}}'
|
|
"""
|
|
|
|
filename = tmp_path / ev.manifest_name
|
|
filename.write_text(content)
|
|
|
|
env("create", "test", str(filename))
|
|
with ev.read("test"):
|
|
install()
|
|
|
|
with ev.read("test") as e:
|
|
# the view root in the included view should NOT exist
|
|
assert not os.path.exists(str(default_dir))
|
|
|
|
for spec in e._get_environment_specs():
|
|
# no specs will exist in the included view projection
|
|
included_spec_subdir = f"{spec.version}-{spec.compiler.name}"
|
|
included_spec_dir = str(view_dir / spec.name / included_spec_subdir)
|
|
assert not os.path.exists(included_spec_dir)
|
|
|
|
# only specs compiled with %gcc (selected in the included view) that
|
|
# are also not cmake (excluded in the environment view) should exist
|
|
env_spec_subdir = f"{spec.compiler.name}-{spec.version}"
|
|
env_spec_dir = str(view_dir / spec.name / env_spec_subdir)
|
|
if spec.satisfies("cmake") or spec.satisfies("%clang"):
|
|
assert not os.path.exists(env_spec_dir)
|
|
else:
|
|
assert os.path.exists(env_spec_dir)
|
|
|
|
|
|
def test_env_view_resolves_identical_file_conflicts(tmp_path, install_mockery, mock_fetch):
|
|
"""When files clash in a view, but refer to the same file on disk (for example, the dependent
|
|
symlinks to a file in the dependency at the same relative path), Spack links the first regular
|
|
file instead of symlinks. This is important for copy type views where we need the underlying
|
|
file to be copied instead of the symlink (when a symlink would be copied, it would become a
|
|
self-referencing symlink after relocation). The test uses a symlink type view though, since
|
|
that keeps track of the original file path."""
|
|
with ev.create("env", with_view=tmp_path / "view") as e:
|
|
add("view-resolve-conflict-top")
|
|
install()
|
|
top = e.matching_spec("view-resolve-conflict-top").prefix
|
|
bottom = e.matching_spec("view-file").prefix
|
|
|
|
# In this example we have `./bin/x` in 3 prefixes, two links, one regular file. We expect the
|
|
# regular file to be linked into the view. There are also 2 links at `./bin/y`, but no regular
|
|
# file, so we expect standard behavior: first entry is linked into the view.
|
|
|
|
# view-resolve-conflict-top/bin/
|
|
# x -> view-file/bin/x
|
|
# y -> view-resolve-conflict-middle/bin/y # expect this y to be linked
|
|
# view-resolve-conflict-middle/bin/
|
|
# x -> view-file/bin/x
|
|
# y -> view-file/bin/x
|
|
# view-file/bin/
|
|
# x # expect this x to be linked
|
|
|
|
assert readlink(tmp_path / "view" / "bin" / "x") == bottom.bin.x
|
|
assert readlink(tmp_path / "view" / "bin" / "y") == top.bin.y
|
|
|
|
|
|
def test_env_view_ignores_different_file_conflicts(tmp_path, install_mockery, mock_fetch):
|
|
"""Test that file-file conflicts for two unique files in environment views are ignored, and
|
|
that the dependent's file is linked into the view, not the dependency's file."""
|
|
with ev.create("env", with_view=tmp_path / "view") as e:
|
|
add("view-ignore-conflict")
|
|
install()
|
|
prefix_dependent = e.matching_spec("view-ignore-conflict").prefix
|
|
# The dependent's file is linked into the view
|
|
assert readlink(tmp_path / "view" / "bin" / "x") == prefix_dependent.bin.x
|