# 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.mock_package import MockPackageMultiRepo
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_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')
remove = SpackCommand('remove')
concretize = SpackCommand('concretize')
stage = SpackCommand('stage')
uninstall = SpackCommand('uninstall')
find = SpackCommand('find')
sep = os.sep
[docs]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')))
[docs]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'])
[docs]def test_add():
e = ev.create('test')
e.add('mpileaks')
assert Spec('mpileaks') in e.user_specs
[docs]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')
[docs]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')
[docs]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
[docs]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
[docs]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)
[docs]def test_env_uninstalled_specs(install_mockery, mock_fetch):
e = ev.create('test')
e.add('cmake-client')
e.concretize()
assert any(s.name == 'cmake-client' for s in e.uninstalled_specs())
e.install_all()
assert not any(s.name == 'cmake-client' for s in e.uninstalled_specs())
e.add('mpileaks')
e.concretize()
assert not any(s.name == 'cmake-client' for s in e.uninstalled_specs())
assert any(s.name == 'mpileaks' for s in e.uninstalled_specs())
[docs]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
[docs]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'
[docs]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
[docs]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
[docs]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'
[docs]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
[docs]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)
[docs]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'
[docs]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)
[docs]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')
[docs]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')
[docs]def test_env_status_broken_view(
mutable_mock_env_path, mock_archive, mock_fetch, mock_packages,
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(MockPackageMultiRepo()):
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
[docs]def test_env_activate_broken_view(
mutable_mock_env_path, mock_archive, mock_fetch, mock_packages,
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
new_repo = MockPackageMultiRepo()
with spack.repo.use_repositories(new_repo):
with pytest.raises(SpackCommandError):
env('activate', '--sh', 'test')
# test replacing repo fixes it
env('activate', '--sh', 'test')
[docs]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
[docs]def test_env_repo():
e = ev.create('test')
e.add('mpileaks')
e.write()
with ev.read('test'):
concretize()
package = e.repo.get('mpileaks')
assert package.name == 'mpileaks'
assert package.namespace == 'builtin.mock'
[docs]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)
[docs]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
[docs]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
[docs]@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']
[docs]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
[docs]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())
[docs]def test_with_config_bad_include(capfd):
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(SystemExit):
with e:
e.concretize()
out, err = capfd.readouterr()
assert 'missing include' in err
assert '/no/such/directory' in err
assert os.path.join('no', 'such', 'file.yaml') in err
assert ev.active_environment() is None
[docs]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'))
[docs]def test_env_with_included_config_file():
test_config = """\
env:
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]
""")
with e:
e.concretize()
assert any(x.satisfies('mpileaks@2.2')
for x in e._get_environment_specs())
[docs]def test_env_with_included_config_scope():
config_scope_path = os.path.join(ev.root('test'), 'config')
test_config = """\
env:
include:
- %s
specs:
- mpileaks
""" % config_scope_path
_env_create('test', StringIO(test_config))
e = ev.read('test')
fs.mkdirp(config_scope_path)
with open(os.path.join(config_scope_path, 'packages.yaml'), 'w') as f:
f.write("""\
packages:
mpileaks:
version: [2.2]
""")
with e:
e.concretize()
assert any(x.satisfies('mpileaks@2.2')
for x in e._get_environment_specs())
[docs]def test_env_with_included_config_var_path():
config_var_path = os.path.join('$tempdir', 'included-config.yaml')
test_config = """\
env:
include:
- %s
specs:
- mpileaks
""" % 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))
with open(config_real_path, 'w') as f:
f.write("""\
packages:
mpileaks:
version: [2.2]
""")
with e:
e.concretize()
assert any(x.satisfies('mpileaks@2.2')
for x in e._get_environment_specs())
[docs]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())
[docs]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()])
[docs]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
[docs]@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')
[docs]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')
[docs]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
[docs]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
[docs]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
[docs]@pytest.mark.usefixtures('config')
def test_indirect_build_dep():
"""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.
"""
default = ('build', 'link')
build_only = ('build',)
mock_repo = MockPackageMultiRepo()
z = mock_repo.add_package('z', [], [])
y = mock_repo.add_package('y', [z], [build_only])
mock_repo.add_package('x', [y], [default])
def noop(*args):
pass
setattr(mock_repo, 'dump_provenance', noop)
with spack.repo.use_repositories(mock_repo):
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
[docs]@pytest.mark.usefixtures('config')
def test_store_different_build_deps():
r"""Ensure that an environment can store two instances of a build-only
dependency::
x y
/| (l) | (b)
(b) | y z2
\| (b)
z1
"""
default = ('build', 'link')
build_only = ('build',)
mock_repo = MockPackageMultiRepo()
z = mock_repo.add_package('z', [], [])
y = mock_repo.add_package('y', [z], [build_only])
mock_repo.add_package('x', [y, z], [default, build_only])
def noop(*args):
pass
setattr(mock_repo, 'dump_provenance', noop)
with spack.repo.use_repositories(mock_repo):
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()
[docs]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)
[docs]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')
[docs]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()
[docs]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'))
[docs]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)
[docs]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'))
[docs]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')))
[docs]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)
[docs]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)
[docs]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)
[docs]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)
[docs]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)
[docs]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
[docs]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
[docs]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
[docs]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
[docs]@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
[docs]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
[docs]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
[docs]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
[docs]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
[docs]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
[docs]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
[docs]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
[docs]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
[docs]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
[docs]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
[docs]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
[docs]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')
[docs]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']
[docs]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)))
[docs]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)))
[docs]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)))
[docs]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)))
[docs]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)))
[docs]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))
[docs]@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')
[docs]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)))
[docs]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
[docs]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
[docs]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)))
[docs]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
[docs]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
[docs]@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
[docs]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())
[docs]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()
[docs]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()
[docs]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()
[docs]@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
[docs]@pytest.fixture
def packages_yaml_v015(tmpdir):
"""Return the path to an existing manifest in the v0.15.x format
and the path to a non yet existing backup file.
"""
raw_yaml = """
spack:
specs:
- mpich
packages:
cmake:
paths:
cmake@3.17.3: /usr
"""
manifest = tmpdir.ensure('spack.yaml')
backup_file = tmpdir.join('spack.yaml.bkp')
manifest.write(raw_yaml)
return manifest, backup_file
[docs]def test_update_anonymous_env(packages_yaml_v015):
manifest, backup_file = packages_yaml_v015
env('update', '-y', str(manifest.dirname))
# The environment is now at the latest format
assert ev.is_latest_format(str(manifest))
# A backup file has been created and it's not at the latest format
assert os.path.exists(str(backup_file))
assert not ev.is_latest_format(str(backup_file))
[docs]def test_double_update(packages_yaml_v015):
manifest, backup_file = packages_yaml_v015
# Update the environment
env('update', '-y', str(manifest.dirname))
# Try to read the environment (it should not error)
ev.create('test', str(manifest))
# Updating again does nothing since the manifest is up-to-date
env('update', '-y', str(manifest.dirname))
# The environment is at the latest format
assert ev.is_latest_format(str(manifest))
# A backup file has been created and it's not at the latest format
assert os.path.exists(str(backup_file))
assert not ev.is_latest_format(str(backup_file))
[docs]def test_update_and_revert(packages_yaml_v015):
manifest, backup_file = packages_yaml_v015
# Update the environment
env('update', '-y', str(manifest.dirname))
assert os.path.exists(str(backup_file))
assert not ev.is_latest_format(str(backup_file))
assert ev.is_latest_format(str(manifest))
# Revert to previous state
env('revert', '-y', str(manifest.dirname))
assert not os.path.exists(str(backup_file))
assert not ev.is_latest_format(str(manifest))
[docs]@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)
[docs]@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))
[docs]@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
[docs]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')
[docs]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')
[docs]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'
[docs]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'
[docs]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'
[docs]@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())
[docs]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
[docs]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
[docs]@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())
[docs]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'))
[docs]@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
[docs]@pytest.mark.regression('26548')
def test_custom_store_in_environment(mutable_config, tmpdir):
spack_yaml = tmpdir.join('spack.yaml')
spack_yaml.write("""
spack:
specs:
- libelf
config:
install_tree:
root: /tmp/store
""")
if sys.platform == 'win32':
sep = '\\'
else:
sep = '/'
current_store_root = str(spack.store.root)
assert str(current_store_root) != sep + os.path.join('tmp', 'store')
with spack.environment.Environment(str(tmpdir)):
assert str(spack.store.root) == sep + os.path.join('tmp', 'store')
assert str(spack.store.root) == current_store_root
[docs]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))
[docs]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)
[docs]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)
[docs]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']
[docs]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
[docs]@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
[docs]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)
[docs]@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
[docs]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):
for task in ('.fetch', '.install'):
tgt = os.path.join('prefix', task, spec.dag_hash())
assert 'touch {}'.format(tgt) in all_out
# Check whether make prefix/fetch-all only fetches
fetch_out = make('prefix/fetch-all', '-n', '-f', makefile, output=str)
assert '.install/' not in fetch_out
assert '.fetch/' in fetch_out
[docs]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()
[docs]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