Source code for spack.test.concretize

# 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 os
import shutil
import sys

import jinja2
import pytest

import archspec.cpu

import llnl.util.lang

import spack.compilers
import spack.concretize
import spack.error
import spack.hash_types as ht
import spack.platforms
import spack.repo
import spack.variant as vt
from spack.concretize import find_spec
from spack.spec import Spec
from spack.util.mock_package import MockPackageMultiRepo
from spack.version import ver

is_windows = sys.platform == 'win32'


[docs]def check_spec(abstract, concrete): if abstract.versions.concrete: assert abstract.versions == concrete.versions if abstract.variants: for name in abstract.variants: avariant = abstract.variants[name] cvariant = concrete.variants[name] assert avariant.value == cvariant.value if abstract.compiler_flags: for flag in abstract.compiler_flags: aflag = abstract.compiler_flags[flag] cflag = concrete.compiler_flags[flag] assert set(aflag) <= set(cflag) for name in abstract.package.variants: assert name in concrete.variants for flag in concrete.compiler_flags.valid_compiler_flags(): assert flag in concrete.compiler_flags if abstract.compiler and abstract.compiler.concrete: assert abstract.compiler == concrete.compiler if abstract.architecture and abstract.architecture.concrete: assert abstract.architecture == concrete.architecture
[docs]def check_concretize(abstract_spec): abstract = Spec(abstract_spec) concrete = abstract.concretized() assert not abstract.concrete assert concrete.concrete check_spec(abstract, concrete) return concrete
[docs]@pytest.fixture( params=[ # no_deps 'libelf', 'libelf@0.8.13', # dag 'callpath', 'mpileaks', 'libelf', # variant 'mpich+debug', 'mpich~debug', 'mpich debug=True', 'mpich', # compiler flags 'mpich cppflags="-O3"', # with virtual 'mpileaks ^mpi', 'mpileaks ^mpi@:1.1', 'mpileaks ^mpi@2:', 'mpileaks ^mpi@2.1', 'mpileaks ^mpi@2.2', 'mpileaks ^mpi@2.2', 'mpileaks ^mpi@:1', 'mpileaks ^mpi@1.2:2', # conflict not triggered 'conflict', 'conflict%clang~foo', 'conflict-parent%gcc' ] ) def spec(request): """Spec to be concretized""" return request.param
[docs]@pytest.fixture(params=[ # Mocking the host detection 'haswell', 'broadwell', 'skylake', 'icelake', # Using preferred targets from packages.yaml 'icelake-preference', 'cannonlake-preference' ]) def current_host(request, monkeypatch): # is_preference is not empty if we want to supply the # preferred target via packages.yaml cpu, _, is_preference = request.param.partition('-') target = archspec.cpu.TARGETS[cpu] monkeypatch.setattr(spack.platforms.Test, 'default', cpu) monkeypatch.setattr(spack.platforms.Test, 'front_end', cpu) if not is_preference: monkeypatch.setattr(archspec.cpu, 'host', lambda: target) yield target else: with spack.config.override('packages:all', {'target': [cpu]}): yield target
[docs]@pytest.fixture() def repo_with_changing_recipe(tmpdir_factory, mutable_mock_repo): repo_namespace = 'changing' repo_dir = tmpdir_factory.mktemp(repo_namespace) repo_dir.join('repo.yaml').write(""" repo: namespace: changing """, ensure=True) packages_dir = repo_dir.ensure('packages', dir=True) root_pkg_str = """ class Root(Package): homepage = "http://www.example.com" url = "http://www.example.com/root-1.0.tar.gz" version(1.0, sha256='abcde') depends_on('changing') conflicts('changing~foo') """ packages_dir.join('root', 'package.py').write( root_pkg_str, ensure=True ) changing_template = """ class Changing(Package): homepage = "http://www.example.com" url = "http://www.example.com/changing-1.0.tar.gz" {% if not delete_version %} version(1.0, sha256='abcde') {% endif %} version(0.9, sha256='abcde') {% if not delete_variant %} variant('fee', default=True, description='nope') {% endif %} variant('foo', default=True, description='nope') {% if add_variant %} variant('fum', default=True, description='nope') variant('fum2', default=True, description='nope') {% endif %} """ class _ChangingPackage(object): default_context = [ ('delete_version', True), ('delete_variant', False), ('add_variant', False) ] def __init__(self, repo_directory): self.repo_dir = repo_directory self.repo = spack.repo.Repo(str(repo_directory)) mutable_mock_repo.put_first(self.repo) def change(self, changes=None): changes = changes or {} context = dict(self.default_context) context.update(changes) # Remove the repo object and delete Python modules mutable_mock_repo.remove(self.repo) # TODO: this mocks a change in the recipe that should happen in a # TODO: different process space. Leaving this comment as a hint # TODO: in case tests using this fixture start failing. if sys.modules.get('spack.pkg.changing.changing'): del sys.modules['spack.pkg.changing.changing'] del sys.modules['spack.pkg.changing.root'] del sys.modules['spack.pkg.changing'] # Change the recipe t = jinja2.Template(changing_template) changing_pkg_str = t.render(**context) packages_dir.join('changing', 'package.py').write( changing_pkg_str, ensure=True ) # Re-add the repository self.repo = spack.repo.Repo(str(self.repo_dir)) mutable_mock_repo.put_first(self.repo) _changing_pkg = _ChangingPackage(repo_dir) _changing_pkg.change({ 'delete_version': False, 'delete_variant': False, 'add_variant': False }) return _changing_pkg
[docs]@pytest.fixture() def additional_repo_with_c(tmpdir_factory, mutable_mock_repo): """Add a repository with a simple package""" repo_dir = tmpdir_factory.mktemp('myrepo') repo_dir.join('repo.yaml').write(""" repo: namespace: myrepo """, ensure=True) packages_dir = repo_dir.ensure('packages', dir=True) package_py = """ class C(Package): homepage = "http://www.example.com" url = "http://www.example.com/root-1.0.tar.gz" version(1.0, sha256='abcde') """ packages_dir.join('c', 'package.py').write(package_py, ensure=True) repo = spack.repo.Repo(str(repo_dir)) mutable_mock_repo.put_first(repo) return repo
# This must use the mutable_config fixture because the test # adjusting_default_target_based_on_compiler uses the current_host fixture, # which changes the config.
[docs]@pytest.mark.usefixtures('mutable_config', 'mock_packages') class TestConcretize(object):
[docs] def test_concretize(self, spec): check_concretize(spec)
[docs] def test_concretize_mention_build_dep(self): spec = check_concretize('cmake-client ^cmake@3.4.3') # Check parent's perspective of child to_dependencies = spec.edges_to_dependencies(name='cmake') assert len(to_dependencies) == 1 assert set(to_dependencies[0].deptypes) == set(['build']) # Check child's perspective of parent cmake = spec['cmake'] from_dependents = cmake.edges_from_dependents(name='cmake-client') assert len(from_dependents) == 1 assert set(from_dependents[0].deptypes) == set(['build'])
[docs] def test_concretize_preferred_version(self): spec = check_concretize('python') assert spec.versions == ver('2.7.11') spec = check_concretize('python@3.5.1') assert spec.versions == ver('3.5.1')
[docs] def test_concretize_with_restricted_virtual(self): check_concretize('mpileaks ^mpich2') concrete = check_concretize('mpileaks ^mpich2@1.1') assert concrete['mpich2'].satisfies('mpich2@1.1') concrete = check_concretize('mpileaks ^mpich2@1.2') assert concrete['mpich2'].satisfies('mpich2@1.2') concrete = check_concretize('mpileaks ^mpich2@:1.5') assert concrete['mpich2'].satisfies('mpich2@:1.5') concrete = check_concretize('mpileaks ^mpich2@:1.3') assert concrete['mpich2'].satisfies('mpich2@:1.3') concrete = check_concretize('mpileaks ^mpich2@:1.2') assert concrete['mpich2'].satisfies('mpich2@:1.2') concrete = check_concretize('mpileaks ^mpich2@:1.1') assert concrete['mpich2'].satisfies('mpich2@:1.1') concrete = check_concretize('mpileaks ^mpich2@1.1:') assert concrete['mpich2'].satisfies('mpich2@1.1:') concrete = check_concretize('mpileaks ^mpich2@1.5:') assert concrete['mpich2'].satisfies('mpich2@1.5:') concrete = check_concretize('mpileaks ^mpich2@1.3.1:1.4') assert concrete['mpich2'].satisfies('mpich2@1.3.1:1.4')
[docs] def test_concretize_enable_disable_compiler_existence_check(self): with spack.concretize.enable_compiler_existence_check(): with pytest.raises( spack.concretize.UnavailableCompilerVersionError): check_concretize('dttop %gcc@100.100') with spack.concretize.disable_compiler_existence_check(): spec = check_concretize('dttop %gcc@100.100') assert spec.satisfies('%gcc@100.100') assert spec['dtlink3'].satisfies('%gcc@100.100')
[docs] def test_concretize_with_provides_when(self): """Make sure insufficient versions of MPI are not in providers list when we ask for some advanced version. """ repo = spack.repo.path assert not any( s.satisfies('mpich2@:1.0') for s in repo.providers_for('mpi@2.1') ) assert not any( s.satisfies('mpich2@:1.1') for s in repo.providers_for('mpi@2.2') ) assert not any( s.satisfies('mpich@:1') for s in repo.providers_for('mpi@2') ) assert not any( s.satisfies('mpich@:1') for s in repo.providers_for('mpi@3') ) assert not any( s.satisfies('mpich2') for s in repo.providers_for('mpi@3') )
[docs] def test_provides_handles_multiple_providers_of_same_version(self): """ """ providers = spack.repo.path.providers_for('mpi@3.0') # Note that providers are repo-specific, so we don't misinterpret # providers, but vdeps are not namespace-specific, so we can # associate vdeps across repos. assert Spec('builtin.mock.multi-provider-mpi@1.10.3') in providers assert Spec('builtin.mock.multi-provider-mpi@1.10.2') in providers assert Spec('builtin.mock.multi-provider-mpi@1.10.1') in providers assert Spec('builtin.mock.multi-provider-mpi@1.10.0') in providers assert Spec('builtin.mock.multi-provider-mpi@1.8.8') in providers
[docs] def test_different_compilers_get_different_flags(self): client = Spec('cmake-client %gcc@4.7.2 platform=test os=fe target=fe' + ' ^cmake %clang@3.5 platform=test os=fe target=fe') client.concretize() cmake = client['cmake'] assert set(client.compiler_flags['cflags']) == set(['-O0', '-g']) assert set(cmake.compiler_flags['cflags']) == set(['-O3']) assert set(client.compiler_flags['fflags']) == set(['-O0', '-g']) assert not set(cmake.compiler_flags['fflags'])
[docs] def test_architecture_inheritance(self): """test_architecture_inheritance is likely to fail with an UnavailableCompilerVersionError if the architecture is concretized incorrectly. """ spec = Spec('cmake-client %gcc@4.7.2 os=fe ^ cmake') spec.concretize() assert spec['cmake'].architecture == spec.architecture
[docs] def test_architecture_deep_inheritance(self, mock_targets): """Make sure that indirect dependencies receive architecture information from the root even when partial architecture information is provided by an intermediate dependency. """ default_dep = ('link', 'build') mock_repo = MockPackageMultiRepo() bazpkg = mock_repo.add_package('bazpkg', [], []) barpkg = mock_repo.add_package('barpkg', [bazpkg], [default_dep]) mock_repo.add_package('foopkg', [barpkg], [default_dep]) with spack.repo.use_repositories(mock_repo): spec = Spec('foopkg %gcc@4.5.0 os=CNL target=nocona' + ' ^barpkg os=CNL ^bazpkg os=CNL') spec.concretize() for s in spec.traverse(root=False): assert s.architecture.target == spec.architecture.target
[docs] def test_compiler_flags_from_user_are_grouped(self): spec = Spec('a%gcc cflags="-O -foo-flag foo-val" platform=test') spec.concretize() cflags = spec.compiler_flags['cflags'] assert any(x == '-foo-flag foo-val' for x in cflags)
[docs] def concretize_multi_provider(self): s = Spec('mpileaks ^multi-provider-mpi@3.0') s.concretize() assert s['mpi'].version == ver('1.10.3')
[docs] def test_concretize_dependent_with_singlevalued_variant_type(self): s = Spec('singlevalue-variant-dependent-type') s.concretize()
[docs] @pytest.mark.parametrize("spec,version", [ ('dealii', 'develop'), ('xsdk', '0.4.0'), ]) def concretize_difficult_packages(self, a, b): """Test a couple of large packages that are often broken due to current limitations in the concretizer""" s = Spec(a + '@' + b) s.concretize() assert s[a].version == ver(b)
[docs] def test_concretize_two_virtuals(self): """Test a package with multiple virtual dependencies.""" Spec('hypre').concretize()
[docs] def test_concretize_two_virtuals_with_one_bound( self, mutable_mock_repo ): """Test a package with multiple virtual dependencies and one preset.""" Spec('hypre ^openblas').concretize()
[docs] def test_concretize_two_virtuals_with_two_bound(self): """Test a package with multiple virtual deps and two of them preset.""" Spec('hypre ^openblas ^netlib-lapack').concretize()
[docs] def test_concretize_two_virtuals_with_dual_provider(self): """Test a package with multiple virtual dependencies and force a provider that provides both. """ Spec('hypre ^openblas-with-lapack').concretize()
[docs] def test_concretize_two_virtuals_with_dual_provider_and_a_conflict( self ): """Test a package with multiple virtual dependencies and force a provider that provides both, and another conflicting package that provides one. """ s = Spec('hypre ^openblas-with-lapack ^netlib-lapack') with pytest.raises(spack.error.SpackError): s.concretize()
[docs] @pytest.mark.skipif(sys.platform == 'win32', reason='No Compiler for Arch on Win') def test_no_matching_compiler_specs(self, mock_low_high_config): # only relevant when not building compilers as needed with spack.concretize.enable_compiler_existence_check(): s = Spec('a %gcc@0.0.0') with pytest.raises( spack.concretize.UnavailableCompilerVersionError): s.concretize()
[docs] def test_no_compilers_for_arch(self): s = Spec('a arch=linux-rhel0-x86_64') with pytest.raises(spack.error.SpackError): s.concretize()
[docs] def test_virtual_is_fully_expanded_for_callpath(self): # force dependence on fake "zmpi" by asking for MPI 10.0 spec = Spec('callpath ^mpi@10.0') assert len(spec.dependencies(name='mpi')) == 1 assert 'fake' not in spec spec.concretize() assert len(spec.dependencies(name='zmpi')) == 1 assert all(not d.dependencies(name='mpi') for d in spec.traverse()) assert all(x in spec for x in ('zmpi', 'mpi')) edges_to_zmpi = spec.edges_to_dependencies(name='zmpi') assert len(edges_to_zmpi) == 1 assert 'fake' in edges_to_zmpi[0].spec
[docs] def test_virtual_is_fully_expanded_for_mpileaks( self ): spec = Spec('mpileaks ^mpi@10.0') assert len(spec.dependencies(name='mpi')) == 1 assert 'fake' not in spec spec.concretize() assert len(spec.dependencies(name='zmpi')) == 1 assert len(spec.dependencies(name='callpath')) == 1 callpath = spec.dependencies(name='callpath')[0] assert len(callpath.dependencies(name='zmpi')) == 1 zmpi = callpath.dependencies(name='zmpi')[0] assert len(zmpi.dependencies(name='fake')) == 1 assert all(not d.dependencies(name='mpi') for d in spec.traverse()) assert all(x in spec for x in ('zmpi', 'mpi'))
[docs] def test_my_dep_depends_on_provider_of_my_virtual_dep(self): spec = Spec('indirect-mpich') spec.normalize() spec.concretize()
[docs] @pytest.mark.parametrize('compiler_str', [ 'clang', 'gcc', 'gcc@4.5.0', 'clang@:3.3.0' ]) def test_compiler_inheritance(self, compiler_str): spec_str = 'mpileaks %{0}'.format(compiler_str) spec = Spec(spec_str).concretized() assert spec['libdwarf'].compiler.satisfies(compiler_str) assert spec['libelf'].compiler.satisfies(compiler_str)
[docs] def test_external_package(self): spec = Spec('externaltool%gcc') spec.concretize() assert spec['externaltool'].external_path == \ os.path.sep + os.path.join('path', 'to', 'external_tool') assert 'externalprereq' not in spec assert spec['externaltool'].compiler.satisfies('gcc')
[docs] def test_external_package_module(self): # No tcl modules on darwin/linux machines # and Windows does not (currently) allow for bash calls # TODO: improved way to check for this. platform = spack.platforms.real_host().name if platform == 'darwin' or platform == 'linux' or platform == 'windows': return spec = Spec('externalmodule') spec.concretize() assert spec['externalmodule'].external_modules == ['external-module'] assert 'externalprereq' not in spec assert spec['externalmodule'].compiler.satisfies('gcc')
[docs] def test_nobuild_package(self): """Test that a non-buildable package raise an error if no specs in packages.yaml are compatible with the request. """ spec = Spec('externaltool%clang') with pytest.raises(spack.error.SpecError): spec.concretize()
[docs] def test_external_and_virtual(self): spec = Spec('externaltest') spec.concretize() assert spec['externaltool'].external_path == \ os.path.sep + os.path.join('path', 'to', 'external_tool') assert spec['stuff'].external_path == \ os.path.sep + os.path.join('path', 'to', 'external_virtual_gcc') assert spec['externaltool'].compiler.satisfies('gcc') assert spec['stuff'].compiler.satisfies('gcc')
[docs] def test_find_spec_parents(self): """Tests the spec finding logic used by concretization. """ s = Spec.from_literal({ 'a +foo': { 'b +foo': { 'c': None, 'd+foo': None }, 'e +foo': None } }) assert 'a' == find_spec(s['b'], lambda s: '+foo' in s).name
[docs] def test_find_spec_children(self): s = Spec.from_literal({ 'a': { 'b +foo': { 'c': None, 'd+foo': None }, 'e +foo': None } }) assert 'd' == find_spec(s['b'], lambda s: '+foo' in s).name s = Spec.from_literal({ 'a': { 'b +foo': { 'c+foo': None, 'd': None }, 'e +foo': None } }) assert 'c' == find_spec(s['b'], lambda s: '+foo' in s).name
[docs] def test_find_spec_sibling(self): s = Spec.from_literal({ 'a': { 'b +foo': { 'c': None, 'd': None }, 'e +foo': None } }) assert 'e' == find_spec(s['b'], lambda s: '+foo' in s).name assert 'b' == find_spec(s['e'], lambda s: '+foo' in s).name s = Spec.from_literal({ 'a': { 'b +foo': { 'c': None, 'd': None }, 'e': { 'f +foo': None } } }) assert 'f' == find_spec(s['b'], lambda s: '+foo' in s).name
[docs] def test_find_spec_self(self): s = Spec.from_literal({ 'a': { 'b +foo': { 'c': None, 'd': None }, 'e': None } }) assert 'b' == find_spec(s['b'], lambda s: '+foo' in s).name
[docs] def test_find_spec_none(self): s = Spec.from_literal({ 'a': { 'b': { 'c': None, 'd': None }, 'e': None } }) assert find_spec(s['b'], lambda s: '+foo' in s) is None
[docs] def test_compiler_child(self): s = Spec('mpileaks%clang target=x86_64 ^dyninst%gcc') s.concretize() assert s['mpileaks'].satisfies('%clang') assert s['dyninst'].satisfies('%gcc')
[docs] def test_conflicts_in_spec(self, conflict_spec): s = Spec(conflict_spec) with pytest.raises(spack.error.SpackError): s.concretize()
[docs] def test_conflicts_show_cores(self, conflict_spec, monkeypatch): if spack.config.get('config:concretizer') == 'original': pytest.skip('Testing debug statements specific to new concretizer') s = Spec(conflict_spec) with pytest.raises(spack.error.SpackError) as e: s.concretize() assert "conflict" in e.value.message
[docs] def test_conflict_in_all_directives_true(self): s = Spec('when-directives-true') with pytest.raises(spack.error.SpackError): s.concretize()
[docs] @pytest.mark.parametrize('spec_str', [ 'conflict@10.0%clang+foo' ]) def test_no_conflict_in_external_specs(self, spec_str): # Modify the configuration to have the spec with conflict # registered as an external ext = Spec(spec_str) data = { 'externals': [ {'spec': spec_str, 'prefix': '/fake/path'} ] } spack.config.set("packages::{0}".format(ext.name), data) ext.concretize() # failure raises exception
[docs] def test_regression_issue_4492(self): # Constructing a spec which has no dependencies, but is otherwise # concrete is kind of difficult. What we will do is to concretize # a spec, and then modify it to have no dependency and reset the # cache values. s = Spec('mpileaks') s.concretize() # Check that now the Spec is concrete, store the hash assert s.concrete # Remove the dependencies and reset caches s.clear_dependencies() s._concrete = False assert not s.concrete
[docs] @pytest.mark.regression('7239') def test_regression_issue_7239(self): # Constructing a SpecBuildInterface from another SpecBuildInterface # results in an inconsistent MRO # Normal Spec s = Spec('mpileaks') s.concretize() assert llnl.util.lang.ObjectWrapper not in type(s).__mro__ # Spec wrapped in a build interface build_interface = s['mpileaks'] assert llnl.util.lang.ObjectWrapper in type(build_interface).__mro__ # Mimics asking the build interface from a build interface build_interface = s['mpileaks']['mpileaks'] assert llnl.util.lang.ObjectWrapper in type(build_interface).__mro__
[docs] @pytest.mark.regression('7705') def test_regression_issue_7705(self): # spec.package.provides(name) doesn't account for conditional # constraints in the concretized spec s = Spec('simple-inheritance~openblas') s.concretize() assert not s.package.provides('lapack')
[docs] @pytest.mark.regression('7941') def test_regression_issue_7941(self): # The string representation of a spec containing # an explicit multi-valued variant and a dependency # might be parsed differently than the originating spec s = Spec('a foobar=bar ^b') t = Spec(str(s)) s.concretize() t.concretize() assert s.dag_hash() == t.dag_hash()
[docs] @pytest.mark.parametrize('abstract_specs', [ # Establish a baseline - concretize a single spec ('mpileaks',), # When concretized together with older version of callpath # and dyninst it uses those older versions ('mpileaks', 'callpath@0.9', 'dyninst@8.1.1'), # Handle recursive syntax within specs ('mpileaks', 'callpath@0.9 ^dyninst@8.1.1', 'dyninst'), # Test specs that have overlapping dependencies but are not # one a dependency of the other ('mpileaks', 'direct-mpich') ]) def test_simultaneous_concretization_of_specs(self, abstract_specs): abstract_specs = [Spec(x) for x in abstract_specs] concrete_specs = spack.concretize.concretize_specs_together( *abstract_specs) # Check there's only one configuration of each package in the DAG names = set( dep.name for spec in concrete_specs for dep in spec.traverse() ) for name in names: name_specs = set( spec[name] for spec in concrete_specs if name in spec ) assert len(name_specs) == 1 # Check that there's at least one Spec that satisfies the # initial abstract request for aspec in abstract_specs: assert any(cspec.satisfies(aspec) for cspec in concrete_specs) # Make sure the concrete spec are top-level specs with no dependents for spec in concrete_specs: assert not spec.dependents()
[docs] @pytest.mark.parametrize('spec', ['noversion', 'noversion-bundle']) def test_noversion_pkg(self, spec): """Test concretization failures for no-version packages.""" with pytest.raises(spack.error.SpackError): Spec(spec).concretized()
[docs] @pytest.mark.skipif(sys.platform == 'win32', reason="Not supported on Windows (yet)") # Include targets to prevent regression on 20537 @pytest.mark.parametrize('spec, best_achievable', [ ('mpileaks%gcc@4.4.7 ^dyninst@10.2.1 target=x86_64:', 'core2'), ('mpileaks%gcc@4.8 target=x86_64:', 'haswell'), ('mpileaks%gcc@5.3.0 target=x86_64:', 'broadwell'), ('mpileaks%apple-clang@5.1.0 target=x86_64:', 'x86_64') ]) @pytest.mark.regression('13361', '20537') def test_adjusting_default_target_based_on_compiler( self, spec, best_achievable, current_host, mock_targets ): best_achievable = archspec.cpu.TARGETS[best_achievable] expected = best_achievable if best_achievable < current_host \ else current_host with spack.concretize.disable_compiler_existence_check(): s = Spec(spec).concretized() assert str(s.architecture.target) == str(expected)
[docs] @pytest.mark.regression('8735,14730') def test_compiler_version_matches_any_entry_in_compilers_yaml(self): # Ensure that a concrete compiler with different compiler version # doesn't match (here it's 4.5 vs. 4.5.0) with pytest.raises(spack.concretize.UnavailableCompilerVersionError): s = Spec('mpileaks %gcc@4.5') s.concretize() # An abstract compiler with a version list could resolve to 4.5.0 s = Spec('mpileaks %gcc@4.5:') s.concretize() assert str(s.compiler.version) == '4.5.0'
[docs] def test_concretize_anonymous(self): with pytest.raises(spack.error.SpackError): s = Spec('+variant') s.concretize()
[docs] @pytest.mark.parametrize('spec_str', [ 'mpileaks ^%gcc', 'mpileaks ^cflags=-g' ]) def test_concretize_anonymous_dep(self, spec_str): with pytest.raises(spack.error.SpackError): s = Spec(spec_str) s.concretize()
[docs] @pytest.mark.parametrize('spec_str,expected_str', [ # Unconstrained versions select default compiler (gcc@4.5.0) ('bowtie@1.3.0', '%gcc@4.5.0'), # Version with conflicts and no valid gcc select another compiler ('bowtie@1.2.2', '%clang@3.3'), # If a higher gcc is available still prefer that ('bowtie@1.2.2 os=redhat6', '%gcc@4.7.2'), ]) def test_compiler_conflicts_in_package_py(self, spec_str, expected_str): if spack.config.get('config:concretizer') == 'original': pytest.xfail('Original concretizer cannot work around conflicts') s = Spec(spec_str).concretized() assert s.satisfies(expected_str)
[docs] @pytest.mark.parametrize('spec_str,expected,unexpected', [ ('conditional-variant-pkg@1.0', ['two_whens'], ['version_based', 'variant_based']), ('conditional-variant-pkg@2.0', ['version_based', 'variant_based'], ['two_whens']), ('conditional-variant-pkg@2.0~version_based', ['version_based'], ['variant_based', 'two_whens']), ('conditional-variant-pkg@2.0+version_based+variant_based', ['version_based', 'variant_based', 'two_whens'], []) ]) def test_conditional_variants(self, spec_str, expected, unexpected): s = Spec(spec_str).concretized() for var in expected: assert s.satisfies('%s=*' % var) for var in unexpected: assert not s.satisfies('%s=*' % var)
[docs] @pytest.mark.parametrize('bad_spec', [ '@1.0~version_based', '@1.0+version_based', '@2.0~version_based+variant_based', '@2.0+version_based~variant_based+two_whens', ]) def test_conditional_variants_fail(self, bad_spec): with pytest.raises( (spack.error.UnsatisfiableSpecError, vt.InvalidVariantForSpecError) ): _ = Spec('conditional-variant-pkg' + bad_spec).concretized()
[docs] @pytest.mark.parametrize('spec_str,expected,unexpected', [ ('py-extension3 ^python@3.5.1', [], ['py-extension1']), ('py-extension3 ^python@2.7.11', ['py-extension1'], []), ('py-extension3@1.0 ^python@2.7.11', ['patchelf@0.9'], []), ('py-extension3@1.1 ^python@2.7.11', ['patchelf@0.9'], []), ('py-extension3@1.0 ^python@3.5.1', ['patchelf@0.10'], []), ]) @pytest.mark.skipif( sys.version_info[:2] == (3, 5), reason='Known failure with Python3.5' ) def test_conditional_dependencies(self, spec_str, expected, unexpected): s = Spec(spec_str).concretized() for dep in expected: msg = '"{0}" is not in "{1}" and was expected' assert dep in s, msg.format(dep, spec_str) for dep in unexpected: msg = '"{0}" is in "{1}" but was unexpected' assert dep not in s, msg.format(dep, spec_str)
[docs] @pytest.mark.parametrize('spec_str,patched_deps', [ ('patch-several-dependencies', [('libelf', 1), ('fake', 2)]), ('patch-several-dependencies@1.0', [('libelf', 1), ('fake', 2), ('libdwarf', 1)]), ('patch-several-dependencies@1.0 ^libdwarf@20111030', [('libelf', 1), ('fake', 2), ('libdwarf', 2)]), ('patch-several-dependencies ^libelf@0.8.10', [('libelf', 2), ('fake', 2)]), ('patch-several-dependencies +foo', [('libelf', 2), ('fake', 2)]) ]) def test_patching_dependencies(self, spec_str, patched_deps): s = Spec(spec_str).concretized() for dep, num_patches in patched_deps: assert s[dep].satisfies('patches=*') assert len(s[dep].variants['patches'].value) == num_patches
[docs] @pytest.mark.regression( '267,303,1781,2310,2632,3628' ) @pytest.mark.parametrize('spec_str, expected', [ # Need to understand that this configuration is possible # only if we use the +mpi variant, which is not the default ('fftw ^mpich', ['+mpi']), # This spec imposes two orthogonal constraints on a dependency, # one of which is conditional. The original concretizer fail since # when it applies the first constraint, it sets the unknown variants # of the dependency to their default values ('quantum-espresso', ['^fftw@1.0+mpi']), # This triggers a conditional dependency on ^fftw@1.0 ('quantum-espresso', ['^openblas']), # This constructs a constraint for a dependency og the type # @x.y:x.z where the lower bound is unconditional, the upper bound # is conditional to having a variant set ('quantum-espresso', ['^libelf@0.8.12']), ('quantum-espresso~veritas', ['^libelf@0.8.13']) ]) def test_working_around_conflicting_defaults(self, spec_str, expected): if spack.config.get('config:concretizer') == 'original': pytest.xfail('Known failure of the original concretizer') s = Spec(spec_str).concretized() assert s.concrete for constraint in expected: assert s.satisfies(constraint)
[docs] @pytest.mark.regression('4635') @pytest.mark.parametrize('spec_str,expected', [ ('cmake', ['%clang']), ('cmake %gcc', ['%gcc']), ('cmake %clang', ['%clang']) ]) def test_external_package_and_compiler_preferences( self, spec_str, expected ): if spack.config.get('config:concretizer') == 'original': pytest.xfail('Known failure of the original concretizer') packages_yaml = { 'all': { 'compiler': ['clang', 'gcc'], }, 'cmake': { 'externals': [ {'spec': 'cmake@3.4.3', 'prefix': '/usr'} ], 'buildable': False } } spack.config.set('packages', packages_yaml) s = Spec(spec_str).concretized() assert s.external for condition in expected: assert s.satisfies(condition)
[docs] @pytest.mark.regression('5651') def test_package_with_constraint_not_met_by_external( self ): """Check that if we have an external package A at version X.Y in packages.yaml, but our spec doesn't allow X.Y as a version, then a new version of A is built that meets the requirements. """ if spack.config.get('config:concretizer') == 'original': pytest.xfail('Known failure of the original concretizer') packages_yaml = { 'libelf': { 'externals': [ {'spec': 'libelf@0.8.13', 'prefix': '/usr'} ] } } spack.config.set('packages', packages_yaml) # quantum-espresso+veritas requires libelf@:0.8.12 s = Spec('quantum-espresso+veritas').concretized() assert s.satisfies('^libelf@0.8.12') assert not s['libelf'].external
[docs] @pytest.mark.regression('9744') def test_cumulative_version_ranges_with_different_length(self): if spack.config.get('config:concretizer') == 'original': pytest.xfail('Known failure of the original concretizer') s = Spec('cumulative-vrange-root').concretized() assert s.concrete assert s.satisfies('^cumulative-vrange-bottom@2.2')
[docs] @pytest.mark.regression('9937') @pytest.mark.skipif( sys.version_info[:2] == (3, 5), reason='Known failure with Python3.5' ) def test_dependency_conditional_on_another_dependency_state(self): root_str = 'variant-on-dependency-condition-root' dep_str = 'variant-on-dependency-condition-a' spec_str = '{0} ^{1}'.format(root_str, dep_str) s = Spec(spec_str).concretized() assert s.concrete assert s.satisfies('^variant-on-dependency-condition-b') s = Spec(spec_str + '+x').concretized() assert s.concrete assert s.satisfies('^variant-on-dependency-condition-b') s = Spec(spec_str + '~x').concretized() assert s.concrete assert not s.satisfies('^variant-on-dependency-condition-b')
[docs] @pytest.mark.regression('8082') @pytest.mark.parametrize('spec_str,expected', [ ('cmake %gcc', '%gcc'), ('cmake %clang', '%clang') ]) def test_compiler_constraint_with_external_package( self, spec_str, expected ): if spack.config.get('config:concretizer') == 'original': pytest.xfail('Known failure of the original concretizer') packages_yaml = { 'cmake': { 'externals': [ {'spec': 'cmake@3.4.3', 'prefix': '/usr'} ], 'buildable': False } } spack.config.set('packages', packages_yaml) s = Spec(spec_str).concretized() assert s.external assert s.satisfies(expected)
[docs] @pytest.mark.regression('20976') @pytest.mark.parametrize('compiler,spec_str,expected,xfailold', [ ('gcc', 'external-common-python %clang', '%clang ^external-common-openssl%gcc ^external-common-gdbm%clang', False), ('clang', 'external-common-python', '%clang ^external-common-openssl%clang ^external-common-gdbm%clang', True) ]) def test_compiler_in_nonbuildable_external_package( self, compiler, spec_str, expected, xfailold ): """Check that the compiler of a non-buildable external package does not spread to other dependencies, unless no other commpiler is specified.""" packages_yaml = { 'external-common-openssl': { 'externals': [ {'spec': 'external-common-openssl@1.1.1i%' + compiler, 'prefix': '/usr'} ], 'buildable': False } } spack.config.set('packages', packages_yaml) s = Spec(spec_str).concretized() if xfailold and spack.config.get('config:concretizer') == 'original': pytest.xfail('This only works on the ASP-based concretizer') assert s.satisfies(expected) assert 'external-common-perl' not in [d.name for d in s.dependencies()]
[docs] def test_external_packages_have_consistent_hash(self): if spack.config.get('config:concretizer') == 'original': pytest.skip('This tests needs the ASP-based concretizer') s, t = Spec('externaltool'), Spec('externaltool') s._old_concretize(), t._new_concretize() assert s.dag_hash() == t.dag_hash()
[docs] def test_external_that_would_require_a_virtual_dependency(self): s = Spec('requires-virtual').concretized() assert s.external assert 'stuff' not in s
[docs] def test_transitive_conditional_virtual_dependency(self): s = Spec('transitive-conditional-virtual-dependency').concretized() # The default for conditional-virtual-dependency is to have # +stuff~mpi, so check that these defaults are respected assert '+stuff' in s['conditional-virtual-dependency'] assert '~mpi' in s['conditional-virtual-dependency'] # 'stuff' is provided by an external package, so check it's present assert 'externalvirtual' in s
[docs] @pytest.mark.regression('20040') def test_conditional_provides_or_depends_on(self): if spack.config.get('config:concretizer') == 'original': pytest.xfail('Known failure of the original concretizer') # Check that we can concretize correctly a spec that can either # provide a virtual or depend on it based on the value of a variant s = Spec('conditional-provider +disable-v1').concretized() assert 'v1-provider' in s assert s['v1'].name == 'v1-provider' assert s['v2'].name == 'conditional-provider'
[docs] @pytest.mark.regression('20079') @pytest.mark.parametrize('spec_str,tests_arg,with_dep,without_dep', [ # Check that True is treated correctly and attaches test deps # to all nodes in the DAG ('a', True, ['a'], []), ('a foobar=bar', True, ['a', 'b'], []), # Check that a list of names activates the dependency only for # packages in that list ('a foobar=bar', ['a'], ['a'], ['b']), ('a foobar=bar', ['b'], ['b'], ['a']), # Check that False disregard test dependencies ('a foobar=bar', False, [], ['a', 'b']), ]) def test_activating_test_dependencies( self, spec_str, tests_arg, with_dep, without_dep ): s = Spec(spec_str).concretized(tests=tests_arg) for pkg_name in with_dep: msg = "Cannot find test dependency in package '{0}'" node = s[pkg_name] assert node.dependencies(deptype='test'), msg.format(pkg_name) for pkg_name in without_dep: msg = "Test dependency in package '{0}' is unexpected" node = s[pkg_name] assert not node.dependencies(deptype='test'), msg.format(pkg_name)
[docs] @pytest.mark.regression('20019') def test_compiler_match_is_preferred_to_newer_version(self): if spack.config.get('config:concretizer') == 'original': pytest.xfail('Known failure of the original concretizer') # This spec depends on openblas. Openblas has a conflict # that doesn't allow newer versions with gcc@4.4.0. Check # that an old version of openblas is selected, rather than # a different compiler for just that node. spec_str = 'simple-inheritance+openblas %gcc@4.4.0 os=redhat6' s = Spec(spec_str).concretized() assert 'openblas@0.2.13' in s assert s['openblas'].satisfies('%gcc@4.4.0')
[docs] @pytest.mark.regression('19981') def test_target_ranges_in_conflicts(self): with pytest.raises(spack.error.SpackError): Spec('impossible-concretization').concretized()
[docs] def test_target_compatibility(self): if spack.config.get('config:concretizer') == 'original': pytest.xfail('Known failure of the original concretizer') with pytest.raises(spack.error.SpackError): Spec('libdwarf target=x86_64 ^libelf target=x86_64_v2').concretized()
[docs] @pytest.mark.regression('20040') def test_variant_not_default(self): s = Spec('ecp-viz-sdk').concretized() # Check default variant value for the package assert '+dep' in s['conditional-constrained-dependencies'] # Check that non-default variant values are forced on the dependency d = s['dep-with-variants'] assert '+foo+bar+baz' in d
[docs] @pytest.mark.regression('20055') def test_custom_compiler_version(self): if spack.config.get('config:concretizer') == 'original': pytest.xfail('Known failure of the original concretizer') s = Spec('a %gcc@foo os=redhat6').concretized() assert '%gcc@foo' in s
[docs] def test_all_patches_applied(self): uuidpatch = 'a60a42b73e03f207433c5579de207c6ed61d58e4d12dd3b5142eb525728d89ea' if not is_windows else 'd0df7988457ec999c148a4a2af25ce831bfaad13954ba18a4446374cb0aef55e' localpatch = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' spec = spack.spec.Spec('conditionally-patch-dependency+jasper') spec.concretize() assert ((uuidpatch, localpatch) == spec['libelf'].variants['patches'].value)
[docs] def test_dont_select_version_that_brings_more_variants_in(self): s = Spec('dep-with-variants-if-develop-root').concretized() assert s['dep-with-variants-if-develop'].satisfies('@1.0')
[docs] @pytest.mark.regression('20244,20736') @pytest.mark.parametrize('spec_str,is_external,expected', [ # These are all externals, and 0_8 is a version not in package.py ('externaltool@1.0', True, '@1.0'), ('externaltool@0.9', True, '@0.9'), ('externaltool@0_8', True, '@0_8'), # This external package is buildable, has a custom version # in packages.yaml that is greater than the ones in package.py # and specifies a variant ('external-buildable-with-variant +baz', True, '@1.1.special +baz'), ('external-buildable-with-variant ~baz', False, '@1.0 ~baz'), ('external-buildable-with-variant@1.0: ~baz', False, '@1.0 ~baz'), # This uses an external version that meets the condition for # having an additional dependency, but the dependency shouldn't # appear in the answer set ('external-buildable-with-variant@0.9 +baz', True, '@0.9'), # This package has an external version declared that would be # the least preferred if Spack had to build it ('old-external', True, '@1.0.0'), ]) def test_external_package_versions(self, spec_str, is_external, expected): s = Spec(spec_str).concretized() assert s.external == is_external assert s.satisfies(expected)
[docs] @pytest.mark.parametrize('dev_first', [True, False]) @pytest.mark.parametrize('spec', [ 'dev-build-test-install', 'dev-build-test-dependent ^dev-build-test-install']) @pytest.mark.parametrize('mock_db', [True, False]) def test_reuse_does_not_overwrite_dev_specs( self, dev_first, spec, mock_db, tmpdir, monkeypatch): """Test that reuse does not mix dev specs with non-dev specs. Tests for either order (dev specs are not reused for non-dev, and non-dev specs are not reused for dev specs) Tests for a spec in which the root is developed and a spec in which a dep is developed. Tests for both reuse from database and reuse from buildcache""" # dev and non-dev specs that are otherwise identical spec = Spec(spec) dev_spec = spec.copy() dev_constraint = 'dev_path=%s' % tmpdir.strpath dev_spec['dev-build-test-install'].constrain(dev_constraint) # run the test in both orders first_spec = dev_spec if dev_first else spec second_spec = spec if dev_first else dev_spec # concretize and setup spack to reuse in the appropriate manner first_spec.concretize() def mock_fn(*args, **kwargs): return [first_spec] if mock_db: monkeypatch.setattr(spack.store.db, 'query', mock_fn) else: monkeypatch.setattr( spack.binary_distribution, 'update_cache_and_get_specs', mock_fn) # concretize and ensure we did not reuse with spack.config.override("concretizer:reuse", True): second_spec.concretize() assert first_spec.dag_hash() != second_spec.dag_hash()
[docs] @pytest.mark.regression('20292') @pytest.mark.parametrize('context', [ {'add_variant': True, 'delete_variant': False}, {'add_variant': False, 'delete_variant': True}, {'add_variant': True, 'delete_variant': True} ]) def test_reuse_installed_packages_when_package_def_changes( self, context, mutable_database, repo_with_changing_recipe ): if spack.config.get('config:concretizer') == 'original': pytest.xfail('Known failure of the original concretizer') # Install a spec root = Spec('root').concretized() dependency = root['changing'].copy() root.package.do_install(fake=True, explicit=True) # Modify package.py repo_with_changing_recipe.change(context) # Try to concretize with the spec installed previously new_root_with_reuse = Spec('root ^/{0}'.format( dependency.dag_hash()) ).concretized() new_root_without_reuse = Spec('root').concretized() # validate that the graphs are the same with reuse, but not without assert ht.build_hash(root) == ht.build_hash(new_root_with_reuse) assert ht.build_hash(root) != ht.build_hash(new_root_without_reuse) # DAG hash should be the same with reuse since only the dependency changed assert root.dag_hash() == new_root_with_reuse.dag_hash() # Structure and package hash will be different without reuse assert root.dag_hash() != new_root_without_reuse.dag_hash()
[docs] @pytest.mark.regression('20784') def test_concretization_of_test_dependencies(self): # With clingo we emit dependency_conditions regardless of the type # of the dependency. We need to ensure that there's at least one # dependency type declared to infer that the dependency holds. s = Spec('test-dep-with-imposed-conditions').concretized() assert 'c' not in s
[docs] @pytest.mark.parametrize('spec_str', [ 'wrong-variant-in-conflicts', 'wrong-variant-in-depends-on' ]) def test_error_message_for_inconsistent_variants(self, spec_str): if spack.config.get('config:concretizer') == 'original': pytest.xfail('Known failure of the original concretizer') s = Spec(spec_str) with pytest.raises(RuntimeError, match='not found in package'): s.concretize()
[docs] @pytest.mark.regression('22533') @pytest.mark.parametrize('spec_str,variant_name,expected_values', [ # Test the default value 'auto' ('mvapich2', 'file_systems', ('auto',)), # Test setting a single value from the disjoint set ('mvapich2 file_systems=lustre', 'file_systems', ('lustre',)), # Test setting multiple values from the disjoint set ('mvapich2 file_systems=lustre,gpfs', 'file_systems', ('lustre', 'gpfs')), ]) def test_mv_variants_disjoint_sets_from_spec( self, spec_str, variant_name, expected_values ): s = Spec(spec_str).concretized() assert set(expected_values) == set(s.variants[variant_name].value)
[docs] @pytest.mark.regression('22533') def test_mv_variants_disjoint_sets_from_packages_yaml(self): external_mvapich2 = { 'mvapich2': { 'buildable': False, 'externals': [{ 'spec': 'mvapich2@2.3.1 file_systems=nfs,ufs', 'prefix': '/usr' }] } } spack.config.set('packages', external_mvapich2) s = Spec('mvapich2').concretized() assert set(s.variants['file_systems'].value) == set(['ufs', 'nfs'])
[docs] @pytest.mark.regression('22596') def test_external_with_non_default_variant_as_dependency(self): # This package depends on another that is registered as an external # with 'buildable: true' and a variant with a non-default value set s = Spec('trigger-external-non-default-variant').concretized() assert '~foo' in s['external-non-default-variant'] assert '~bar' in s['external-non-default-variant'] assert s['external-non-default-variant'].external
[docs] @pytest.mark.regression('22871') @pytest.mark.parametrize('spec_str,expected_os', [ ('mpileaks', 'os=debian6'), # To trigger the bug in 22871 we need to have the same compiler # spec available on both operating systems ('mpileaks%gcc@4.5.0 platform=test os=debian6', 'os=debian6'), ('mpileaks%gcc@4.5.0 platform=test os=redhat6', 'os=redhat6') ]) def test_os_selection_when_multiple_choices_are_possible( self, spec_str, expected_os ): s = Spec(spec_str).concretized() for node in s.traverse(): assert node.satisfies(expected_os)
[docs] @pytest.mark.regression('22718') @pytest.mark.parametrize('spec_str,expected_compiler', [ ('mpileaks', '%gcc@4.5.0'), ('mpileaks ^mpich%clang@3.3', '%clang@3.3') ]) def test_compiler_is_unique(self, spec_str, expected_compiler): s = Spec(spec_str).concretized() for node in s.traverse(): assert node.satisfies(expected_compiler)
[docs] @pytest.mark.parametrize('spec_str,expected_dict', [ # Check the defaults from the package (libs=shared) ('multivalue-variant', { 'libs=shared': True, 'libs=static': False }), # Check that libs=static doesn't extend the default ('multivalue-variant libs=static', { 'libs=shared': False, 'libs=static': True }), ]) def test_multivalued_variants_from_cli(self, spec_str, expected_dict): s = Spec(spec_str).concretized() for constraint, value in expected_dict.items(): assert s.satisfies(constraint) == value
[docs] @pytest.mark.regression('22351') @pytest.mark.parametrize('spec_str,expected', [ # Version 1.1.0 is deprecated and should not be selected, unless we # explicitly asked for that ('deprecated-versions', ['deprecated-versions@1.0.0']), ('deprecated-versions@1.1.0', ['deprecated-versions@1.1.0']), ]) def test_deprecated_versions_not_selected(self, spec_str, expected): if spack.config.get('config:concretizer') == 'original': pytest.xfail('Known failure of the original concretizer') s = Spec(spec_str).concretized() for abstract_spec in expected: assert abstract_spec in s
[docs] @pytest.mark.regression('24196') def test_version_badness_more_important_than_default_mv_variants(self): # If a dependency had an old version that for some reason pulls in # a transitive dependency with a multi-valued variant, that old # version was preferred because of the order of our optimization # criteria. s = spack.spec.Spec('root').concretized() assert s['gmt'].satisfies('@2.0')
[docs] @pytest.mark.regression('24205') def test_provider_must_meet_requirements(self): # A package can be a provider of a virtual only if the underlying # requirements are met. s = spack.spec.Spec('unsat-virtual-dependency') with pytest.raises((RuntimeError, spack.error.UnsatisfiableSpecError)): s.concretize()
[docs] @pytest.mark.regression('23951') def test_newer_dependency_adds_a_transitive_virtual(self): # Ensure that a package doesn't concretize any of its transitive # dependencies to an old version because newer versions pull in # a new virtual dependency. The possible concretizations here are: # # root@1.0 <- middle@1.0 <- leaf@2.0 <- blas # root@1.0 <- middle@1.0 <- leaf@1.0 # # and "blas" is pulled in only by newer versions of "leaf" s = spack.spec.Spec('root-adds-virtual').concretized() assert s['leaf-adds-virtual'].satisfies('@2.0') assert 'blas' in s
[docs] @pytest.mark.regression('26718') def test_versions_in_virtual_dependencies(self): # Ensure that a package that needs a given version of a virtual # package doesn't end up using a later implementation s = spack.spec.Spec('hpcviewer@2019.02').concretized() assert s['java'].satisfies('virtual-with-versions@1.8.0')
[docs] @pytest.mark.regression('26866') def test_non_default_provider_of_multiple_virtuals(self): s = spack.spec.Spec( 'many-virtual-consumer ^low-priority-provider' ).concretized() assert s['mpi'].name == 'low-priority-provider' assert s['lapack'].name == 'low-priority-provider' for virtual_pkg in ('mpi', 'lapack'): for pkg in spack.repo.path.providers_for(virtual_pkg): if pkg.name == 'low-priority-provider': continue assert pkg not in s
[docs] @pytest.mark.regression('27237') @pytest.mark.parametrize('spec_str,expect_installed', [ ('mpich', True), ('mpich+debug', False), ('mpich~debug', True) ]) def test_concrete_specs_are_not_modified_on_reuse( self, mutable_database, spec_str, expect_installed, config ): if spack.config.get('config:concretizer') == 'original': pytest.xfail('Original concretizer cannot reuse specs') # Test the internal consistency of solve + DAG reconstruction # when reused specs are added to the mix. This prevents things # like additional constraints being added to concrete specs in # the answer set produced by clingo. with spack.config.override("concretizer:reuse", True): s = spack.spec.Spec(spec_str).concretized() assert s.installed is expect_installed assert s.satisfies(spec_str, strict=True)
[docs] @pytest.mark.regression('26721,19736') def test_sticky_variant_in_package(self): if spack.config.get('config:concretizer') == 'original': pytest.xfail('Original concretizer cannot use sticky variants') # Here we test that a sticky variant cannot be changed from its default value # by the ASP solver if not set explicitly. The package used in the test needs # to have +allow-gcc set to be concretized with %gcc and clingo is not allowed # to change the default ~allow-gcc with pytest.raises(spack.error.SpackError): spack.spec.Spec('sticky-variant %gcc').concretized() s = spack.spec.Spec('sticky-variant+allow-gcc %gcc').concretized() assert s.satisfies('%gcc') and s.satisfies('+allow-gcc') s = spack.spec.Spec('sticky-variant %clang').concretized() assert s.satisfies('%clang') and s.satisfies('~allow-gcc')
[docs] def test_do_not_invent_new_concrete_versions_unless_necessary(self): if spack.config.get('config:concretizer') == 'original': pytest.xfail( "Original concretizer doesn't resolve concrete versions to known ones" ) # ensure we select a known satisfying version rather than creating # a new '2.7' version. assert ver("2.7.11") == Spec("python@2.7").concretized().version # Here there is no known satisfying version - use the one on the spec. assert ver("2.7.21") == Spec("python@2.7.21").concretized().version
[docs] @pytest.mark.parametrize('spec_str', [ 'conditional-values-in-variant@1.62.0 cxxstd=17', 'conditional-values-in-variant@1.62.0 cxxstd=2a', 'conditional-values-in-variant@1.72.0 cxxstd=2a', # Ensure disjoint set of values work too 'conditional-values-in-variant@1.72.0 staging=flexpath', ]) def test_conditional_values_in_variants(self, spec_str): if spack.config.get('config:concretizer') == 'original': pytest.skip( "Original concretizer doesn't resolve conditional values in variants" ) s = Spec(spec_str) with pytest.raises((RuntimeError, spack.error.UnsatisfiableSpecError)): s.concretize()
[docs] def test_conditional_values_in_conditional_variant(self): """Test that conditional variants play well with conditional possible values""" if spack.config.get('config:concretizer') == 'original': pytest.skip( "Original concretizer doesn't resolve conditional values in variants" ) s = Spec('conditional-values-in-variant@1.50.0').concretized() assert 'cxxstd' not in s.variants s = Spec('conditional-values-in-variant@1.60.0').concretized() assert 'cxxstd' in s.variants
[docs] def test_target_granularity(self): if spack.config.get('config:concretizer') == 'original': pytest.skip( 'Original concretizer cannot account for target granularity' ) # The test architecture uses core2 as the default target. Check that when # we configure Spack for "generic" granularity we concretize for x86_64 s = Spec('python') assert s.concretized().satisfies('target=core2') with spack.config.override('concretizer:targets', {'granularity': 'generic'}): assert s.concretized().satisfies('target=x86_64')
[docs] def test_host_compatible_concretization(self): if spack.config.get('config:concretizer') == 'original': pytest.skip( 'Original concretizer cannot account for host compatibility' ) # Check that after setting "host_compatible" to false we cannot concretize. # Here we use "k10" to set a target non-compatible with the current host # to avoid a lot of boilerplate when mocking the test platform. The issue # is that the defaults for the test platform are very old, so there's no # compiler supporting e.g. icelake etc. s = Spec('python target=k10') assert s.concretized() with spack.config.override('concretizer:targets', {'host_compatible': True}): with pytest.raises(spack.error.SpackError): s.concretized()
[docs] def test_add_microarchitectures_on_explicit_request(self): if spack.config.get('config:concretizer') == 'original': pytest.skip( 'Original concretizer cannot account for host compatibility' ) # Check that if we consider only "generic" targets, we can still solve for # specific microarchitectures on explicit requests with spack.config.override('concretizer:targets', {'granularity': 'generic'}): s = Spec('python target=k10').concretized() assert s.satisfies('target=k10')
[docs] @pytest.mark.regression('29201') def test_delete_version_and_reuse( self, mutable_database, repo_with_changing_recipe ): """Test that we can reuse installed specs with versions not declared in package.py """ if spack.config.get('config:concretizer') == 'original': pytest.xfail('Known failure of the original concretizer') root = Spec('root').concretized() root.package.do_install(fake=True, explicit=True) repo_with_changing_recipe.change({'delete_version': True}) with spack.config.override("concretizer:reuse", True): new_root = Spec('root').concretized() assert root.dag_hash() == new_root.dag_hash()
[docs] @pytest.mark.regression('29201') def test_installed_version_is_selected_only_for_reuse( self, mutable_database, repo_with_changing_recipe ): """Test that a version coming from an installed spec is a possible version only for reuse """ if spack.config.get('config:concretizer') == 'original': pytest.xfail('Known failure of the original concretizer') # Install a dependency that cannot be reused with "root" # because of a conflict a variant, then delete its version dependency = Spec('changing@1.0~foo').concretized() dependency.package.do_install(fake=True, explicit=True) repo_with_changing_recipe.change({'delete_version': True}) with spack.config.override("concretizer:reuse", True): new_root = Spec('root').concretized() assert not new_root['changing'].satisfies('@1.0')
[docs] @pytest.mark.regression('28259') def test_reuse_with_unknown_namespace_dont_raise( self, additional_repo_with_c, mutable_mock_repo ): s = Spec('c').concretized() assert s.namespace == 'myrepo' s.package.do_install(fake=True, explicit=True) # TODO: To mock repo removal we need to recreate the RepoPath mutable_mock_repo.remove(additional_repo_with_c) spack.repo.path = spack.repo.RepoPath(*spack.repo.path.repos) with spack.config.override("concretizer:reuse", True): s = Spec('c').concretized() assert s.namespace == 'builtin.mock'
[docs] @pytest.mark.regression('28259') def test_reuse_with_unknown_package_dont_raise( self, additional_repo_with_c, mutable_mock_repo, monkeypatch ): s = Spec('c').concretized() assert s.namespace == 'myrepo' s.package.do_install(fake=True, explicit=True) # Here we delete the package.py instead of removing the repo and we # make it such that "c" doesn't exist in myrepo del sys.modules['spack.pkg.myrepo.c'] c_dir = os.path.join(additional_repo_with_c.root, 'packages', 'c') shutil.rmtree(c_dir) monkeypatch.setattr(additional_repo_with_c, 'exists', lambda x: False) with spack.config.override("concretizer:reuse", True): s = Spec('c').concretized() assert s.namespace == 'builtin.mock'
[docs] @pytest.mark.parametrize('specs,expected', [ (['libelf', 'libelf@0.8.10'], 1), (['libdwarf%gcc', 'libelf%clang'], 2), (['libdwarf%gcc', 'libdwarf%clang'], 4), (['libdwarf^libelf@0.8.12', 'libdwarf^libelf@0.8.13'], 4), (['hdf5', 'zmpi'], 3), (['hdf5', 'mpich'], 2), (['hdf5^zmpi', 'mpich'], 4), (['mpi', 'zmpi'], 2), (['mpi', 'mpich'], 1), ]) def test_best_effort_coconcretize(self, specs, expected): import spack.solver.asp if spack.config.get('config:concretizer') == 'original': pytest.skip('Original concretizer cannot concretize in rounds') specs = [spack.spec.Spec(s) for s in specs] solver = spack.solver.asp.Solver() solver.reuse = False concrete_specs = set() for result in solver.solve_in_rounds(specs): for s in result.specs: concrete_specs.update(s.traverse()) assert len(concrete_specs) == expected
[docs] @pytest.mark.parametrize('specs,expected_spec,occurances', [ # The algorithm is greedy, and it might decide to solve the "best" # spec early in which case reuse is suboptimal. In this case the most # recent version of libdwarf is selected and concretized to libelf@0.8.13 (['libdwarf@20111030^libelf@0.8.10', 'libdwarf@20130207^libelf@0.8.12', 'libdwarf@20130729'], 'libelf@0.8.12', 1), # Check we reuse the best libelf in the environment (['libdwarf@20130729^libelf@0.8.10', 'libdwarf@20130207^libelf@0.8.12', 'libdwarf@20111030'], 'libelf@0.8.12', 2), (['libdwarf@20130729', 'libdwarf@20130207', 'libdwarf@20111030'], 'libelf@0.8.13', 3), # We need to solve in 2 rounds and we expect mpich to be preferred to zmpi (['hdf5+mpi', 'zmpi', 'mpich'], 'mpich', 2) ]) def test_best_effort_coconcretize_preferences( self, specs, expected_spec, occurances ): """Test package preferences during coconcretization.""" import spack.solver.asp if spack.config.get('config:concretizer') == 'original': pytest.skip('Original concretizer cannot concretize in rounds') specs = [spack.spec.Spec(s) for s in specs] solver = spack.solver.asp.Solver() solver.reuse = False concrete_specs = {} for result in solver.solve_in_rounds(specs): concrete_specs.update(result.specs_by_input) counter = 0 for spec in concrete_specs.values(): if expected_spec in spec: counter += 1 assert counter == occurances, concrete_specs
[docs] @pytest.mark.regression('30864') def test_misleading_error_message_on_version(self, mutable_database): # For this bug to be triggered we need a reusable dependency # that is not optimal in terms of optimization scores. # We pick an old version of "b" import spack.solver.asp if spack.config.get('config:concretizer') == 'original': pytest.skip('Original concretizer cannot reuse') reusable_specs = [ spack.spec.Spec('non-existing-conditional-dep@1.0').concretized() ] root_spec = spack.spec.Spec('non-existing-conditional-dep@2.0') with spack.config.override("concretizer:reuse", True): solver = spack.solver.asp.Solver() setup = spack.solver.asp.SpackSolverSetup() with pytest.raises(spack.solver.asp.UnsatisfiableSpecError, match="'dep-with-variants' satisfies '@999'"): solver.driver.solve(setup, [root_spec], reuse=reusable_specs)
[docs] @pytest.mark.regression('31148') def test_version_weight_and_provenance(self): """Test package preferences during coconcretization.""" import spack.solver.asp if spack.config.get('config:concretizer') == 'original': pytest.skip('Original concretizer cannot reuse') reusable_specs = [ spack.spec.Spec(spec_str).concretized() for spec_str in ('b@0.9', 'b@1.0') ] root_spec = spack.spec.Spec('a foobar=bar') with spack.config.override("concretizer:reuse", True): solver = spack.solver.asp.Solver() setup = spack.solver.asp.SpackSolverSetup() result = solver.driver.solve( setup, [root_spec], reuse=reusable_specs, out=sys.stdout ) # The result here should have a single spec to build ('a') # and it should be using b@1.0 with a version badness of 2 # The provenance is: # version_declared("b","1.0",0,"package_py"). # version_declared("b","0.9",1,"package_py"). # version_declared("b","1.0",2,"installed"). # version_declared("b","0.9",3,"installed"). for criterion in [ (1, None, 'number of packages to build (vs. reuse)'), (2, 0, 'version badness') ]: assert criterion in result.criteria assert result.specs[0].satisfies('^b@1.0')
[docs] @pytest.mark.regression('31169') def test_not_reusing_incompatible_os_or_compiler(self): import spack.solver.asp if spack.config.get('config:concretizer') == 'original': pytest.skip('Original concretizer cannot reuse') root_spec = spack.spec.Spec('b') s = root_spec.concretized() wrong_compiler, wrong_os = s.copy(), s.copy() wrong_compiler.compiler = spack.spec.CompilerSpec('gcc@12.1.0') wrong_os.architecture = spack.spec.ArchSpec('test-ubuntu2204-x86_64') reusable_specs = [wrong_compiler, wrong_os] with spack.config.override("concretizer:reuse", True): solver = spack.solver.asp.Solver() setup = spack.solver.asp.SpackSolverSetup() result = solver.driver.solve( setup, [root_spec], reuse=reusable_specs, out=sys.stdout ) concrete_spec = result.specs[0] assert concrete_spec.satisfies('%gcc@4.5.0') assert concrete_spec.satisfies('os=debian6')