Source code for spack.test.cmd.env

# 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_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_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)
[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_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)
[docs]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)
[docs]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
[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_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]def test_old_format_cant_be_updated_implicitly(packages_yaml_v015): manifest, backup_file = packages_yaml_v015 env('activate', str(manifest.dirname)) with pytest.raises(spack.main.SpackCommandError): add('hdf5')
[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('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
[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_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