spack/lib/spack/spack/test/cmd/env.py
Massimiliano Culpo de8c827983
Refactor a few classes related to package repositories (#32273)
Caches used by repositories don't reference the global spack.repo.path instance
anymore, but get the repository they refer to during initialization.
 
Spec.virtual now use the index, and computation done to compute the index 
use Repository.is_virtual_safe. 

Code to construct mock packages and mock repository has been factored into 
a unique MockRepositoryBuilder that is used throughout the codebase.

Add debug print for pushing and popping config scopes.

Changed spack.repo.use_repositories so that it can override or not previous repos

spack.repo.use_repositories updates spack.config.config according to the modifications done

Removed a peculiar behavior from spack.config.Configuration where push would always 
bubble-up a scope named command_line if it existed
2022-10-11 19:28:27 +02:00

3106 lines
88 KiB
Python

# Copyright 2013-2022 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 os
import shutil
import sys
from argparse import Namespace
import pytest
from six import StringIO
import llnl.util.filesystem as fs
import llnl.util.link_tree
import spack.cmd.env
import spack.environment as ev
import spack.environment.shell
import spack.modules
import spack.paths
import spack.repo
import spack.util.spack_json as sjson
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.util.web import FetchError
from spack.version import Version
# TODO-27021
# everything here uses the mock_env_path
pytestmark = [
pytest.mark.usefixtures("mutable_mock_env_path", "config", "mutable_mock_repo"),
pytest.mark.maybeslow,
pytest.mark.skipif(sys.platform == "win32", reason="Envs unsupported on Window"),
]
env = SpackCommand("env")
install = SpackCommand("install")
add = SpackCommand("add")
change = SpackCommand("change")
remove = SpackCommand("remove")
concretize = SpackCommand("concretize")
stage = SpackCommand("stage")
uninstall = SpackCommand("uninstall")
find = SpackCommand("find")
sep = os.sep
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.satisfies("mpileaks@2.2") for x in e.user_specs)
assert any(x.satisfies("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.satisfies("%gcc") for x in e.user_specs if x.name == "mpileaks")
assert any(x.satisfies("%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.satisfies("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.join(".DS_Store").ensure(file=True)
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(spack.main.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_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("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"
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.db.query(explicit=True)
dependency = spack.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.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("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: couldn't get environment settings" 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("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_install_same_spec_twice(install_mockery, mock_fetch):
env("create", "test")
e = ev.read("test")
with e:
# The first installation outputs the package prefix, updates the view
out = install("cmake-client")
assert "Updating view at" in out
# The second installation reports all packages already installed
out = install("cmake-client")
assert "already installed" in out
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(
"""\
env:
specs:
- 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.db.query_one("depb", installed=True)
assert depb, "Expected depb to be installed"
a = spack.store.db.query_one("a", installed=True)
assert a, "Expected 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_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_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.Environment("local_dir"):
with capsys.disabled():
assert os.path.join(os.getcwd(), "local_dir") in env("status")
e = ev.Environment("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,
tmpdir,
):
env_dir = str(tmpdir)
with ev.Environment(env_dir):
install("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(env_dir):
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(env_dir):
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("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):
with pytest.raises(SpackCommandError):
env("activate", "--sh", "test")
# test replacing repo fixes it
env("activate", "--sh", "test")
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():
"""Ensure a user can remove from any position in the spack.yaml file."""
initial_yaml = StringIO(
"""\
env:
specs:
- mpileaks
- hypre
- libelf
"""
)
before = ev.create("test", initial_yaml)
before.concretize()
before.write()
# user modifies yaml externally to spack and removes hypre
with open(before.manifest_path, "w") as f:
f.write(
"""\
env:
specs:
- mpileaks
- libelf
"""
)
after = ev.read("test")
after.concretize()
after.write()
env_specs = after._get_environment_specs()
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(tmpdir):
"""Test that an environment can be instantiated from a lockfile."""
initial_yaml = StringIO(
"""\
env:
specs:
- mpileaks
- hypre
- libelf
"""
)
e1 = ev.create("test", initial_yaml)
e1.concretize()
e1.write()
e2 = ev.Environment(str(tmpdir), 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(tmpdir):
"""Test that an environment can be instantiated from a lockfile."""
initial_yaml = StringIO(
"""\
env:
specs:
- mpileaks
- hypre
- libelf
"""
)
e1 = ev.create("test", initial_yaml)
e1.concretize()
e1.write()
e2 = ev.Environment(str(tmpdir), 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
@pytest.mark.usefixtures("config")
def test_env_view_external_prefix(tmpdir_factory, mutable_database, mock_packages):
fake_prefix = tmpdir_factory.mktemp("a-prefix")
fake_bin = fake_prefix.join("bin")
fake_bin.ensure(dir=True)
initial_yaml = StringIO(
"""\
env:
specs:
- a
view: true
"""
)
external_config = StringIO(
"""\
packages:
a:
externals:
- spec: 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", initial_yaml)
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_default_view_to_env(env_mod)
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(
"""\
env:
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():
test_config = """\
env:
specs:
- mpileaks
packages:
mpileaks:
version: [2.2]
"""
_env_create("test", StringIO(test_config))
e = ev.read("test")
with e:
e.concretize()
assert any(x.satisfies("mpileaks@2.2") for x in e._get_environment_specs())
def test_with_config_bad_include():
env_name = "test_bad_include"
test_config = """\
spack:
include:
- /no/such/directory
- no/such/file.yaml
"""
_env_create(env_name, StringIO(test_config))
e = ev.read(env_name)
with pytest.raises(spack.config.ConfigFileError) as exc:
with e:
e.concretize()
err = str(exc)
assert "not retrieve configuration" in err
assert os.path.join("no", "such", "directory") in err
assert ev.active_environment() is None
def test_env_with_include_config_files_same_basename():
test_config = """\
env:
include:
- ./path/to/included-config.yaml
- ./second/path/to/include-config.yaml
specs:
[libelf, mpileaks]
"""
_env_create("test", StringIO(test_config))
e = ev.read("test")
fs.mkdirp(os.path.join(e.path, "path", "to"))
with open(os.path.join(e.path, "./path/to/included-config.yaml"), "w") as f:
f.write(
"""\
packages:
libelf:
version: [0.8.10]
"""
)
fs.mkdirp(os.path.join(e.path, "second", "path", "to"))
with open(os.path.join(e.path, "./second/path/to/include-config.yaml"), "w") as f:
f.write(
"""\
packages:
mpileaks:
version: [2.2]
"""
)
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 """\
env:
include:
- {0}
specs:
- mpileaks
""".format(
include_path
)
def test_env_with_included_config_file(packages_file):
"""Test inclusion of a relative packages configuration file added to an
existing environment."""
include_filename = "included-config.yaml"
test_config = mpileaks_env_config(os.path.join(".", include_filename))
_env_create("test", StringIO(test_config))
e = ev.read("test")
included_path = os.path.join(e.path, include_filename)
shutil.move(packages_file.strpath, included_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_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)
scopes = env.included_config_scopes()
assert len(scopes) == 1
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))
env = ev.Environment(tmpdir.strpath)
with pytest.raises(FetchError, match="No such file or directory"):
ev.activate(env)
def test_env_with_included_config_scope(tmpdir, 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."""
config_scope_path = os.path.join(ev.root("test"), "config")
# Configure the environment to include file(s) from the environment's
# remote configuration stage directory.
test_config = mpileaks_env_config(config_scope_path)
# Create the environment
_env_create("test", StringIO(test_config))
e = ev.read("test")
# 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 = os.path.join(config_scope_path, include_filename)
fs.copy(packages_file.strpath, included_path)
# Ensure the concretized environment reflects contents of the
# packages.yaml file.
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(packages_file):
"""Test inclusion of a package configuration file with path variables
"staged" in the environment's configuration stage directory."""
config_var_path = os.path.join("$tempdir", "included-config.yaml")
test_config = mpileaks_env_config(config_var_path)
_env_create("test", StringIO(test_config))
e = ev.read("test")
config_real_path = substitute_path_variables(config_var_path)
fs.mkdirp(os.path.dirname(config_real_path))
shutil.move(packages_file.strpath, config_real_path)
assert os.path.exists(config_real_path)
with e:
e.concretize()
assert any(x.satisfies("mpileaks@2.2") for x in e._get_environment_specs())
def test_env_config_precedence():
test_config = """\
env:
packages:
libelf:
version: [0.8.12]
include:
- ./included-config.yaml
specs:
- mpileaks
"""
_env_create("test", StringIO(test_config))
e = ev.read("test")
with open(os.path.join(e.path, "included-config.yaml"), "w") as f:
f.write(
"""\
packages:
mpileaks:
version: [2.2]
libelf:
version: [0.8.11]
"""
)
with e:
e.concretize()
# ensure included scope took effect
assert any(x.satisfies("mpileaks@2.2") for x in e._get_environment_specs())
# ensure env file takes precedence
assert any(x.satisfies("libelf@0.8.12") for x in e._get_environment_specs())
def test_included_config_precedence():
test_config = """\
env:
include:
- ./high-config.yaml # this one should take precedence
- ./low-config.yaml
specs:
- mpileaks
"""
_env_create("test", StringIO(test_config))
e = ev.read("test")
with open(os.path.join(e.path, "high-config.yaml"), "w") as f:
f.write(
"""\
packages:
libelf:
version: [0.8.10] # this should override libelf version below
"""
)
with open(os.path.join(e.path, "low-config.yaml"), "w") as f:
f.write(
"""\
packages:
mpileaks:
version: [2.2]
libelf:
version: [0.8.12]
"""
)
with e:
e.concretize()
assert any(x.satisfies("mpileaks@2.2") for x in e._get_environment_specs())
assert any([x.satisfies("libelf@0.8.10") for x in e._get_environment_specs()])
def test_bad_env_yaml_format(tmpdir):
filename = str(tmpdir.join("spack.yaml"))
with open(filename, "w") as f:
f.write(
"""\
env:
spacks:
- mpileaks
"""
)
with tmpdir.as_cwd():
with pytest.raises(spack.config.ConfigFormatError) as e:
env("create", "test", "./spack.yaml")
assert "./spack.yaml:2" in str(e)
assert "'spacks' was unexpected" in str(e)
def test_env_loads(install_mockery, mock_fetch):
env("create", "test")
with ev.read("test"):
add("mpileaks")
concretize()
install("--fake")
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(spack.main.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("mpileaks", fail_on_error=False)
assert uninstall.returncode == 1
assert "used by the following environments" 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_removes_from_env(mock_stage, mock_fetch, install_mockery):
env("create", "test")
with ev.read("test"):
add("mpileaks")
add("libelf")
install("--fake")
test = ev.read("test")
assert any(s.name == "mpileaks" for s in test.specs_by_hash.values())
assert any(s.name == "libelf" for s in test.specs_by_hash.values())
with ev.read("test"):
uninstall("-ya")
test = ev.read("test")
assert not test.specs_by_hash
assert not test.concretized_order
assert not test.user_specs
@pytest.mark.usefixtures("config")
def test_indirect_build_dep(tmpdir):
"""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(tmpdir)
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
@pytest.mark.usefixtures("config")
def test_store_different_build_deps(tmpdir):
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(tmpdir)
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(
llnl.util.link_tree.MergeConflictSummary, match=spack.store.layout.metadata_dir
):
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-dir-file")
add("view-dir-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-dir-symlinked-dir")
add("view-dir-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)
def test_env_config_view_default(tmpdir, mock_stage, mock_fetch, install_mockery):
# This config doesn't mention whether a view is enabled
test_config = """\
env:
specs:
- mpileaks
"""
_env_create("test", StringIO(test_config))
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", "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", "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("--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(
"""\
env:
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(
"""\
env:
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(
"""\
env:
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(
"""\
env:
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(
"""\
env:
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(
"""\
env:
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(tmpdir):
filename = str(tmpdir.join("spack.yaml"))
with open(filename, "w") as f:
f.write(
"""\
env:
definitions:
- packages: [mpileaks, callpath]
specs:
- matrix:
- [$packages]
- [^mpich, ^zmpi]
"""
)
with tmpdir.as_cwd():
env("create", "test", "./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(
"""\
env:
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(
"""\
env:
definitions:
- packages:
- matrix:
- [mpileaks, callpath]
- [target=be]
specs:
- $packages
"""
)
with tmpdir.as_cwd():
env("create", "test", "./spack.yaml")
with ev.read("test") as 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_concretize_extraneous_deps(tmpdir, config, mock_packages):
# FIXME: The new concretizer doesn't handle yet soft
# FIXME: constraints for stacks
# FIXME: This now works for statically-determinable invalid deps
# FIXME: But it still does not work for dynamically determined invalid deps
# if spack.config.get('config:concretizer') == 'clingo':
# pytest.skip('Clingo concretizer does not support soft constraints')
filename = str(tmpdir.join("spack.yaml"))
with open(filename, "w") as f:
f.write(
"""\
env:
definitions:
- packages: [libelf, mpileaks]
- install:
- matrix:
- [$packages]
- ['^zmpi', '^mpich']
specs:
- $install
"""
)
with tmpdir.as_cwd():
env("create", "test", "./spack.yaml")
with ev.read("test"):
concretize()
test = ev.read("test")
for user, concrete in test.concretized_specs():
assert concrete.concrete
assert not user.concrete
if user.name == "libelf":
assert not concrete.satisfies("^mpi", strict=True)
elif user.name == "mpileaks":
assert concrete.satisfies("^mpi", strict=True)
def test_stack_concretize_extraneous_variants(tmpdir, config, mock_packages):
filename = str(tmpdir.join("spack.yaml"))
with open(filename, "w") as f:
f.write(
"""\
env:
definitions:
- packages: [libelf, mpileaks]
- install:
- matrix:
- [$packages]
- ['~shared', '+shared']
specs:
- $install
"""
)
with tmpdir.as_cwd():
env("create", "test", "./spack.yaml")
with ev.read("test"):
concretize()
test = ev.read("test")
for user, concrete in test.concretized_specs():
assert concrete.concrete
assert not user.concrete
if user.name == "libelf":
assert "shared" not in concrete.variants
if user.name == "mpileaks":
assert concrete.variants["shared"].value == user.variants["shared"].value
def test_stack_concretize_extraneous_variants_with_dash(tmpdir, config, mock_packages):
filename = str(tmpdir.join("spack.yaml"))
with open(filename, "w") as f:
f.write(
"""\
env:
definitions:
- packages: [libelf, mpileaks]
- install:
- matrix:
- [$packages]
- ['shared=False', '+shared-libs']
specs:
- $install
"""
)
with tmpdir.as_cwd():
env("create", "test", "./spack.yaml")
with ev.read("test"):
concretize()
ev.read("test")
# Regression test for handling of variants with dashes in them
# will fail before this point if code regresses
assert True
def test_stack_definition_extension(tmpdir):
filename = str(tmpdir.join("spack.yaml"))
with open(filename, "w") as f:
f.write(
"""\
env:
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(
"""\
env:
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(
"""\
env:
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(
"""\
env:
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(
"""\
env:
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(
"""\
env:
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(
"""\
env:
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(
"""\
env:
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.yaml["env"]["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(
"""\
env:
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(
"""\
env:
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(
"""\
env:
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(
"""\
env:
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(
"""\
env:
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(
"""\
env:
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(
"""\
env:
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(
"""\
env:
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(
"""\
env:
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
def test_stack_view_multiple_views(
tmpdir, mock_fetch, mock_packages, mock_archive, install_mockery
):
filename = str(tmpdir.join("spack.yaml"))
default_viewdir = str(tmpdir.join("default-view"))
combin_viewdir = str(tmpdir.join("combinatorial-view"))
with open(filename, "w") as f:
f.write(
"""\
env:
definitions:
- packages: [mpileaks, cmake]
- compilers: ['%%gcc', '%%clang']
specs:
- matrix:
- [$packages]
- [$compilers]
view:
default:
root: %s
select: ['%%gcc']
combinatorial:
root: %s
exclude: [callpath %%gcc]
projections:
'all': '{name}/{version}-{compiler.name}'"""
% (default_viewdir, combin_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(default_viewdir, "bin") in shell
test = ev.read("test")
for spec in test._get_environment_specs():
if not spec.satisfies("callpath%gcc"):
assert os.path.exists(
os.path.join(
combin_viewdir, spec.name, "%s-%s" % (spec.version, spec.compiler.name)
)
)
else:
assert not os.path.exists(
os.path.join(
combin_viewdir, spec.name, "%s-%s" % (spec.version, spec.compiler.name)
)
)
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", add_view=True)
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", add_view=True)
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", add_view=True)
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_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")
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())
# 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_cant_install_single_spec_when_concretizing_together():
e = ev.create("coconcretization")
e.unify = True
with pytest.raises(ev.SpackEnvironmentError, match=r"cannot install"):
e.concretize_and_add("zlib")
e.install_all()
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")
with pytest.raises(ev.SpackEnvironmentError, match=r"cannot contain more"):
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 = """\
env:
specs:
- matrix:
- [mpileaks]
packages:
mpileaks:
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.parametrize("concretization,unify", [("together", "true"), ("separately", "false")])
def test_update_concretization_to_concretizer_unify(concretization, unify, tmpdir):
spack_yaml = """\
spack:
concretization: {}
""".format(
concretization
)
tmpdir.join("spack.yaml").write(spack_yaml)
# Update the environment
env("update", "-y", str(tmpdir))
with open(str(tmpdir.join("spack.yaml"))) as f:
assert (
f.read()
== """\
spack:
concretizer:
unify: {}
""".format(
unify
)
)
@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, x, y):
raise RuntimeError("some error")
monkeypatch.setattr(ev.Environment, "_update_and_write_manifest", _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_rewrite_rel_dev_path_original_dir(tmpdir):
"""Relative devevelop paths should not be rewritten when initializing an
environment with root path set to the same directory"""
init_env, _, _, spack_yaml = _setup_develop_packages(tmpdir)
with ev.Environment(str(init_env), str(spack_yaml)) as e:
assert e.dev_specs["mypkg1"]["path"] == "../build_folder"
assert e.dev_specs["mypkg2"]["path"] == "/some/other/path"
def test_rewrite_rel_dev_path_create_original_dir(tmpdir):
"""Relative develop paths should not be rewritten when creating an
environment in the original directory"""
init_env, _, _, spack_yaml = _setup_develop_packages(tmpdir)
env("create", "-d", str(init_env), str(spack_yaml))
with ev.Environment(str(init_env)) as e:
assert e.dev_specs["mypkg1"]["path"] == "../build_folder"
assert e.dev_specs["mypkg2"]["path"] == "/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("hdf5@myversion" in spec for _, spec in e.concretized_specs())
def test_modules_relative_to_views(tmpdir, install_mockery, mock_fetch):
spack_yaml = """
spack:
specs:
- trivial-install-test-package
modules:
default:
enable:: [tcl]
use_view: true
roots:
tcl: modules
"""
_env_create("test", StringIO(spack_yaml))
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_multiple_modules_post_env_hook(tmpdir, install_mockery, mock_fetch):
spack_yaml = """
spack:
specs:
- trivial-install-test-package
modules:
default:
enable:: [tcl]
use_view: true
roots:
tcl: modules
full:
enable:: [tcl]
roots:
tcl: full_modules
"""
_env_create("test", StringIO(spack_yaml))
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]
full_modules_glob = "%s/full_modules/**/*" % e.path
full_modules = glob.glob(full_modules_glob)
assert len(full_modules) == 1
full_module = full_modules[0]
with open(module, "r") as f:
contents = f.read()
with open(full_module, "r") as f:
full_contents = f.read()
assert view_prefix in contents
assert spec.prefix not in contents
assert view_prefix not in full_contents
assert spec.prefix in full_contents
@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():
"""Test whether a spec is develop'ed or not"""
env("create", "test")
with ev.read("test") as e:
e.add("mpich")
e.add("mpileaks")
e.develop(Spec("mpich@1"), "here", clone=False)
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", activate_env="a", env=env, no_env=no_env, env_dir=env_dir)
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.root)
assert str(current_store_root) != install_root
with spack.environment.Environment(str(tmpdir)):
assert str(spack.store.root) == install_root
assert str(spack.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))
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(tmpdir, mock_stage, mock_fetch, install_mockery):
"""Tests whether Spack cleans up after itself when a view fails to create"""
view = str(tmpdir.join("view"))
with ev.create("env", with_view=view):
add("libelf")
install("--fake")
# Save the current view directory.
resolved_view = os.path.realpath(view)
all_views = os.path.dirname(resolved_view)
views_before = os.listdir(all_views)
# Add a spec that results in MergeConflictError's when creating a view
with ev.read("env"):
add("libelf cflags=-O3")
with pytest.raises(llnl.util.link_tree.MergeConflictError):
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 = os.listdir(all_views)
assert views_before == views_after
assert os.path.samefile(resolved_view, view)
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(config, 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("test.lock"))
shutil.copy(lockfile_path, test_lockfile_path)
_env_create("test", 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(config, tmpdir):
"""When reading a version-1 lockfile, make sure that a backup of that file
is created.
"""
# read in the JSON from a legacy v1 lockfile
v1_lockfile_path = os.path.join(spack.paths.test_path, "data", "legacy_env", "v1.lock")
# make an env out of the old lockfile
test_lockfile_path = str(tmpdir.join(ev.lockfile_name))
shutil.copy(v1_lockfile_path, test_lockfile_path)
e = ev.Environment(str(tmpdir))
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):
# 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.
legacy_lockfile_path = os.path.join(
spack.paths.test_path, "data", "legacy_env", "%s.lock" % lockfile
)
# 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", 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 test_environment_depfile_makefile(tmpdir, mock_packages):
env("create", "test")
make = Executable("make")
makefile = str(tmpdir.join("Makefile"))
with ev.read("test"):
add("libdwarf")
concretize()
# Disable jobserver so we can do a dry run.
with ev.read("test"):
env(
"depfile", "-o", makefile, "--make-disable-jobserver", "--make-target-prefix", "prefix"
)
# Do make dry run.
all_out = make("-n", "-f", makefile, output=str)
# Check whether `make` installs everything
with ev.read("test") as e:
for _, root in e.concretized_specs():
for spec in root.traverse(root=True):
tgt = os.path.join("prefix", ".install", spec.dag_hash())
assert "touch {}".format(tgt) in all_out
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_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)
scopes = env.included_config_scopes()
assert len(scopes) == 1
cfg = spack.config.get("packages")
assert "openmpi" in cfg["all"]["providers"]["mpi"]