# 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 sys
import pytest
import spack.directives
import spack.error
from spack.error import SpecError, UnsatisfiableSpecError
from spack.spec import (
Spec,
SpecFormatSigilError,
SpecFormatStringError,
UnconstrainableDependencySpecError,
UnsupportedCompilerError,
)
from spack.variant import (
InvalidVariantValueError,
MultipleValuesInExclusiveVariantError,
UnknownVariantError,
substitute_abstract_variants,
)
[docs]def make_spec(spec_like, concrete):
if isinstance(spec_like, Spec):
return spec_like
spec = Spec(spec_like)
if concrete:
spec._mark_concrete()
substitute_abstract_variants(spec)
return spec
def _specify(spec_like):
if isinstance(spec_like, Spec):
return spec_like
return Spec(spec_like)
[docs]def check_satisfies(target_spec, constraint_spec, target_concrete=False):
target = make_spec(target_spec, target_concrete)
constraint = _specify(constraint_spec)
# Satisfies is one-directional.
assert target.satisfies(constraint)
# If target satisfies constraint, then we should be able to constrain
# constraint by target. Reverse is not always true.
constraint.copy().constrain(target)
[docs]def check_unsatisfiable(target_spec, constraint_spec, target_concrete=False):
target = make_spec(target_spec, target_concrete)
constraint = _specify(constraint_spec)
assert not target.satisfies(constraint)
with pytest.raises(UnsatisfiableSpecError):
constraint.copy().constrain(target)
[docs]def check_constrain(expected, spec, constraint):
exp = Spec(expected)
spec = Spec(spec)
constraint = Spec(constraint)
spec.constrain(constraint)
assert exp == spec
[docs]def check_constrain_changed(spec, constraint):
spec = Spec(spec)
assert spec.constrain(constraint)
[docs]def check_constrain_not_changed(spec, constraint):
spec = Spec(spec)
assert not spec.constrain(constraint)
[docs]def check_invalid_constraint(spec, constraint):
spec = Spec(spec)
constraint = Spec(constraint)
with pytest.raises((UnsatisfiableSpecError,
UnconstrainableDependencySpecError)):
spec.constrain(constraint)
[docs]@pytest.mark.usefixtures('config', 'mock_packages')
class TestSpecSematics(object):
"""This tests satisfies(), constrain() and other semantic operations
on specs.
"""
[docs] def test_satisfies(self):
check_satisfies('libelf@0.8.13', '@0:1')
check_satisfies('libdwarf^libelf@0.8.13', '^libelf@0:1')
[docs] def test_empty_satisfies(self):
# Basic satisfaction
check_satisfies('libelf', Spec())
check_satisfies('libdwarf', Spec())
check_satisfies('%intel', Spec())
check_satisfies('^mpi', Spec())
check_satisfies('+debug', Spec())
check_satisfies('@3:', Spec())
# Concrete (strict) satisfaction
check_satisfies('libelf', Spec(), True)
check_satisfies('libdwarf', Spec(), True)
check_satisfies('%intel', Spec(), True)
check_satisfies('^mpi', Spec(), True)
# TODO: Variants can't be called concrete while anonymous
# check_satisfies('+debug', Spec(), True)
check_satisfies('@3:', Spec(), True)
# Reverse (non-strict) satisfaction
check_satisfies(Spec(), 'libelf')
check_satisfies(Spec(), 'libdwarf')
check_satisfies(Spec(), '%intel')
check_satisfies(Spec(), '^mpi')
# TODO: Variant matching is auto-strict
# we should rethink this
# check_satisfies(Spec(), '+debug')
check_satisfies(Spec(), '@3:')
[docs] def test_satisfies_namespace(self):
check_satisfies('builtin.mpich', 'mpich')
check_satisfies('builtin.mock.mpich', 'mpich')
# TODO: only works for deps now, but shouldn't we allow for root spec?
# check_satisfies('builtin.mock.mpich', 'mpi')
check_satisfies('builtin.mock.mpich', 'builtin.mock.mpich')
check_unsatisfiable('builtin.mock.mpich', 'builtin.mpich')
[docs] def test_satisfies_namespaced_dep(self):
"""Ensure spec from same or unspecified namespace satisfies namespace
constraint."""
check_satisfies('mpileaks ^builtin.mock.mpich', '^mpich')
check_satisfies('mpileaks ^builtin.mock.mpich', '^mpi')
check_satisfies(
'mpileaks ^builtin.mock.mpich', '^builtin.mock.mpich')
check_unsatisfiable(
'mpileaks ^builtin.mock.mpich', '^builtin.mpich')
[docs] def test_satisfies_compiler(self):
check_satisfies('foo%gcc', '%gcc')
check_satisfies('foo%intel', '%intel')
check_unsatisfiable('foo%intel', '%gcc')
check_unsatisfiable('foo%intel', '%pgi')
[docs] def test_satisfies_compiler_version(self):
check_satisfies('foo%gcc', '%gcc@4.7.2')
check_satisfies('foo%intel', '%intel@4.7.2')
check_satisfies('foo%pgi@4.5', '%pgi@4.4:4.6')
check_satisfies('foo@2.0%pgi@4.5', '@1:3%pgi@4.4:4.6')
check_unsatisfiable('foo%pgi@4.3', '%pgi@4.4:4.6')
check_unsatisfiable('foo@4.0%pgi', '@1:3%pgi')
check_unsatisfiable('foo@4.0%pgi@4.5', '@1:3%pgi@4.4:4.6')
check_satisfies('foo %gcc@4.7.3', '%gcc@4.7')
check_unsatisfiable('foo %gcc@4.7', '%gcc@4.7.3')
[docs] def test_satisfies_architecture(self):
check_satisfies(
'foo platform=test',
'platform=test')
check_satisfies(
'foo platform=linux',
'platform=linux')
check_satisfies(
'foo platform=test',
'platform=test target=frontend')
check_satisfies(
'foo platform=test',
'platform=test os=frontend target=frontend')
check_satisfies(
'foo platform=test os=frontend target=frontend',
'platform=test')
check_unsatisfiable(
'foo platform=linux',
'platform=test os=redhat6 target=x86')
check_unsatisfiable(
'foo os=redhat6',
'platform=test os=debian6 target=x86_64')
check_unsatisfiable(
'foo target=x86_64',
'platform=test os=redhat6 target=x86')
check_satisfies(
'foo arch=test-None-None',
'platform=test')
check_satisfies(
'foo arch=test-None-frontend',
'platform=test target=frontend')
check_satisfies(
'foo arch=test-frontend-frontend',
'platform=test os=frontend target=frontend')
check_satisfies(
'foo arch=test-frontend-frontend',
'platform=test')
check_unsatisfiable(
'foo arch=test-frontend-frontend',
'platform=test os=frontend target=backend')
check_satisfies(
'foo platform=test target=frontend os=frontend',
'platform=test target=frontend os=frontend')
check_satisfies(
'foo platform=test target=backend os=backend',
'platform=test target=backend os=backend')
check_satisfies(
'foo platform=test target=default_target os=default_os',
'platform=test os=default_os')
check_unsatisfiable(
'foo platform=test target=x86 os=redhat6',
'platform=linux target=x86 os=redhat6')
[docs] def test_satisfies_dependencies(self):
check_satisfies('mpileaks^mpich', '^mpich')
check_satisfies('mpileaks^zmpi', '^zmpi')
check_unsatisfiable('mpileaks^mpich', '^zmpi')
check_unsatisfiable('mpileaks^zmpi', '^mpich')
[docs] def test_satisfies_dependency_versions(self):
check_satisfies('mpileaks^mpich@2.0', '^mpich@1:3')
check_unsatisfiable('mpileaks^mpich@1.2', '^mpich@2.0')
check_satisfies(
'mpileaks^mpich@2.0^callpath@1.5', '^mpich@1:3^callpath@1.4:1.6')
check_unsatisfiable(
'mpileaks^mpich@4.0^callpath@1.5', '^mpich@1:3^callpath@1.4:1.6')
check_unsatisfiable(
'mpileaks^mpich@2.0^callpath@1.7', '^mpich@1:3^callpath@1.4:1.6')
check_unsatisfiable(
'mpileaks^mpich@4.0^callpath@1.7', '^mpich@1:3^callpath@1.4:1.6')
[docs] def test_satisfies_virtual_dependencies(self):
check_satisfies('mpileaks^mpi', '^mpi')
check_satisfies('mpileaks^mpi', '^mpich')
check_satisfies('mpileaks^mpi', '^zmpi')
check_unsatisfiable('mpileaks^mpich', '^zmpi')
[docs] def test_satisfies_virtual_dependency_versions(self):
check_satisfies('mpileaks^mpi@1.5', '^mpi@1.2:1.6')
check_unsatisfiable('mpileaks^mpi@3', '^mpi@1.2:1.6')
check_satisfies('mpileaks^mpi@2:', '^mpich')
check_satisfies('mpileaks^mpi@2:', '^mpich@3.0.4')
check_satisfies('mpileaks^mpi@2:', '^mpich2@1.4')
check_satisfies('mpileaks^mpi@1:', '^mpich2')
check_satisfies('mpileaks^mpi@2:', '^mpich2')
check_unsatisfiable('mpileaks^mpi@3:', '^mpich2@1.4')
check_unsatisfiable('mpileaks^mpi@3:', '^mpich2')
check_unsatisfiable('mpileaks^mpi@3:', '^mpich@1.0')
[docs] def test_satisfies_matching_variant(self):
check_satisfies('mpich+foo', 'mpich+foo')
check_satisfies('mpich~foo', 'mpich~foo')
check_satisfies('mpich foo=1', 'mpich foo=1')
# confirm that synonymous syntax works correctly
check_satisfies('mpich+foo', 'mpich foo=True')
check_satisfies('mpich foo=true', 'mpich+foo')
check_satisfies('mpich~foo', 'mpich foo=FALSE')
check_satisfies('mpich foo=False', 'mpich~foo')
check_satisfies('mpich foo=*', 'mpich~foo')
check_satisfies('mpich +foo', 'mpich foo=*')
[docs] def test_satisfies_multi_value_variant(self):
# Check quoting
check_satisfies('multivalue-variant foo="bar,baz"',
'multivalue-variant foo="bar,baz"')
check_satisfies('multivalue-variant foo=bar,baz',
'multivalue-variant foo=bar,baz')
check_satisfies('multivalue-variant foo="bar,baz"',
'multivalue-variant foo=bar,baz')
# A more constrained spec satisfies a less constrained one
check_satisfies('multivalue-variant foo="bar,baz"',
'multivalue-variant foo=*')
check_satisfies('multivalue-variant foo=*',
'multivalue-variant foo="bar,baz"')
check_satisfies('multivalue-variant foo="bar,baz"',
'multivalue-variant foo="bar"')
check_satisfies('multivalue-variant foo="bar,baz"',
'multivalue-variant foo="baz"')
check_satisfies('multivalue-variant foo="bar,baz,barbaz"',
'multivalue-variant foo="bar,baz"')
check_satisfies('multivalue-variant foo="bar,baz"',
'foo="bar,baz"')
check_satisfies('multivalue-variant foo="bar,baz"',
'foo="bar"')
[docs] def test_satisfies_single_valued_variant(self):
"""Tests that the case reported in
https://github.com/spack/spack/pull/2386#issuecomment-282147639
is handled correctly.
"""
a = Spec('a foobar=bar')
a.concretize()
assert a.satisfies('foobar=bar')
assert a.satisfies('foobar=*')
# Assert that an autospec generated from a literal
# gives the right result for a single valued variant
assert 'foobar=bar' in a
assert 'foobar=baz' not in a
assert 'foobar=fee' not in a
# ... and for a multi valued variant
assert 'foo=bar' in a
# Check that conditional dependencies are treated correctly
assert '^b' in a
[docs] def test_unsatisfied_single_valued_variant(self):
a = Spec('a foobar=baz')
a.concretize()
assert '^b' not in a
mv = Spec('multivalue-variant')
mv.concretize()
assert 'a@1.0' not in mv
[docs] def test_indirect_unsatisfied_single_valued_variant(self):
spec = Spec('singlevalue-variant-dependent')
spec.concretize()
assert 'a@1.0' not in spec
[docs] def test_unsatisfiable_multi_value_variant(self):
# Semantics for a multi-valued variant is different
# Depending on whether the spec is concrete or not
a = make_spec(
'multivalue-variant foo="bar"', concrete=True
)
spec_str = 'multivalue-variant foo="bar,baz"'
b = Spec(spec_str)
assert not a.satisfies(b)
assert not a.satisfies(spec_str)
# A concrete spec cannot be constrained further
with pytest.raises(UnsatisfiableSpecError):
a.constrain(b)
a = Spec('multivalue-variant foo="bar"')
spec_str = 'multivalue-variant foo="bar,baz"'
b = Spec(spec_str)
# The specs are abstract and they **could** be constrained
assert a.satisfies(b)
assert a.satisfies(spec_str)
# An abstract spec can instead be constrained
assert a.constrain(b)
a = make_spec(
'multivalue-variant foo="bar,baz"', concrete=True
)
spec_str = 'multivalue-variant foo="bar,baz,quux"'
b = Spec(spec_str)
assert not a.satisfies(b)
assert not a.satisfies(spec_str)
# A concrete spec cannot be constrained further
with pytest.raises(UnsatisfiableSpecError):
a.constrain(b)
a = Spec('multivalue-variant foo="bar,baz"')
spec_str = 'multivalue-variant foo="bar,baz,quux"'
b = Spec(spec_str)
# The specs are abstract and they **could** be constrained
assert a.satisfies(b)
assert a.satisfies(spec_str)
# An abstract spec can instead be constrained
assert a.constrain(b)
# ...but will fail during concretization if there are
# values in the variant that are not allowed
with pytest.raises(InvalidVariantValueError):
a.concretize()
# This time we'll try to set a single-valued variant
a = Spec('multivalue-variant fee="bar"')
spec_str = 'multivalue-variant fee="baz"'
b = Spec(spec_str)
# The specs are abstract and they **could** be constrained,
# as before concretization I don't know which type of variant
# I have (if it is not a BV)
assert a.satisfies(b)
assert a.satisfies(spec_str)
# A variant cannot be parsed as single-valued until we try to
# concretize. This means that we can constrain the variant above
assert a.constrain(b)
# ...but will fail during concretization if there are
# multiple values set
with pytest.raises(MultipleValuesInExclusiveVariantError):
a.concretize()
[docs] def test_unsatisfiable_variant_types(self):
# These should fail due to incompatible types
# FIXME: these needs to be checked as the new relaxed
# FIXME: semantic makes them fail (constrain does not raise)
# check_unsatisfiable('multivalue-variant +foo',
# 'multivalue-variant foo="bar"')
# check_unsatisfiable('multivalue-variant ~foo',
# 'multivalue-variant foo="bar"')
check_unsatisfiable(
target_spec='multivalue-variant foo="bar"',
constraint_spec='multivalue-variant +foo',
target_concrete=True
)
check_unsatisfiable(
target_spec='multivalue-variant foo="bar"',
constraint_spec='multivalue-variant ~foo',
target_concrete=True
)
[docs] def test_satisfies_unconstrained_variant(self):
# only asked for mpich, no constraints. Either will do.
check_satisfies('mpich+foo', 'mpich')
check_satisfies('mpich~foo', 'mpich')
check_satisfies('mpich foo=1', 'mpich')
[docs] def test_unsatisfiable_variants(self):
# This case is different depending on whether the specs are concrete.
# 'mpich' is not concrete:
check_satisfies('mpich', 'mpich+foo', False)
check_satisfies('mpich', 'mpich~foo', False)
check_satisfies('mpich', 'mpich foo=1', False)
# 'mpich' is concrete:
check_unsatisfiable('mpich', 'mpich+foo', True)
check_unsatisfiable('mpich', 'mpich~foo', True)
check_unsatisfiable('mpich', 'mpich foo=1', True)
[docs] def test_unsatisfiable_variant_mismatch(self):
# No matchi in specs
check_unsatisfiable('mpich~foo', 'mpich+foo')
check_unsatisfiable('mpich+foo', 'mpich~foo')
check_unsatisfiable('mpich foo=True', 'mpich foo=False')
[docs] def test_satisfies_matching_compiler_flag(self):
check_satisfies('mpich cppflags="-O3"', 'mpich cppflags="-O3"')
check_satisfies(
'mpich cppflags="-O3 -Wall"', 'mpich cppflags="-O3 -Wall"'
)
[docs] def test_satisfies_unconstrained_compiler_flag(self):
# only asked for mpich, no constraints. Any will do.
check_satisfies('mpich cppflags="-O3"', 'mpich')
[docs] def test_unsatisfiable_compiler_flag(self):
# This case is different depending on whether the specs are concrete.
# 'mpich' is not concrete:
check_satisfies('mpich', 'mpich cppflags="-O3"', False)
# 'mpich' is concrete:
check_unsatisfiable('mpich', 'mpich cppflags="-O3"', True)
[docs] def test_copy_satisfies_transitive(self):
spec = Spec('dttop')
spec.concretize()
copy = spec.copy()
for s in spec.traverse():
assert s.satisfies(copy[s.name])
assert copy[s.name].satisfies(s)
[docs] def test_unsatisfiable_compiler_flag_mismatch(self):
# No matchi in specs
check_unsatisfiable(
'mpich cppflags="-O3"', 'mpich cppflags="-O2"')
[docs] def test_satisfies_virtual(self):
# Don't use check_satisfies: it checks constrain() too, and
# you can't constrain a non-virtual by a virtual.
assert Spec('mpich').satisfies(Spec('mpi'))
assert Spec('mpich2').satisfies(Spec('mpi'))
assert Spec('zmpi').satisfies(Spec('mpi'))
[docs] def test_satisfies_virtual_dep_with_virtual_constraint(self):
"""Ensure we can satisfy virtual constraints when there are multiple
vdep providers in the specs."""
assert Spec('netlib-lapack ^openblas').satisfies(
'netlib-lapack ^openblas'
)
assert not Spec('netlib-lapack ^netlib-blas').satisfies(
'netlib-lapack ^openblas'
)
assert not Spec('netlib-lapack ^openblas').satisfies(
'netlib-lapack ^netlib-blas'
)
assert Spec('netlib-lapack ^netlib-blas').satisfies(
'netlib-lapack ^netlib-blas'
)
[docs] def test_satisfies_same_spec_with_different_hash(self):
"""Ensure that concrete specs are matched *exactly* by hash."""
s1 = Spec('mpileaks').concretized()
s2 = s1.copy()
assert s1.satisfies(s2)
assert s2.satisfies(s1)
# Simulate specs that were installed before and after a change to
# Spack's hashing algorithm. This just reverses s2's hash.
s2._hash = s1.dag_hash()[-1::-1]
assert not s1.satisfies(s2)
assert not s2.satisfies(s1)
# ========================================================================
# Indexing specs
# ========================================================================
[docs] def test_self_index(self):
s = Spec('callpath')
assert s['callpath'] == s
[docs] def test_dep_index(self):
s = Spec('callpath')
s.normalize()
assert s['callpath'] == s
assert type(s['dyninst']) == Spec
assert type(s['libdwarf']) == Spec
assert type(s['libelf']) == Spec
assert type(s['mpi']) == Spec
assert s['dyninst'].name == 'dyninst'
assert s['libdwarf'].name == 'libdwarf'
assert s['libelf'].name == 'libelf'
assert s['mpi'].name == 'mpi'
[docs] def test_spec_contains_deps(self):
s = Spec('callpath')
s.normalize()
assert 'dyninst' in s
assert 'libdwarf' in s
assert 'libelf' in s
assert 'mpi' in s
[docs] @pytest.mark.usefixtures('config')
def test_virtual_index(self):
s = Spec('callpath')
s.concretize()
s_mpich = Spec('callpath ^mpich')
s_mpich.concretize()
s_mpich2 = Spec('callpath ^mpich2')
s_mpich2.concretize()
s_zmpi = Spec('callpath ^zmpi')
s_zmpi.concretize()
assert s['mpi'].name != 'mpi'
assert s_mpich['mpi'].name == 'mpich'
assert s_mpich2['mpi'].name == 'mpich2'
assert s_zmpi['zmpi'].name == 'zmpi'
for spec in [s, s_mpich, s_mpich2, s_zmpi]:
assert 'mpi' in spec
# ========================================================================
# Constraints
# ========================================================================
[docs] def test_constrain_variants(self):
check_constrain('libelf@2.1:2.5', 'libelf@0:2.5', 'libelf@2.1:3')
check_constrain(
'libelf@2.1:2.5%gcc@4.5:4.6',
'libelf@0:2.5%gcc@2:4.6',
'libelf@2.1:3%gcc@4.5:4.7'
)
check_constrain('libelf+debug+foo', 'libelf+debug', 'libelf+foo')
check_constrain(
'libelf+debug+foo', 'libelf+debug', 'libelf+debug+foo'
)
check_constrain(
'libelf debug=2 foo=1', 'libelf debug=2', 'libelf foo=1'
)
check_constrain(
'libelf debug=2 foo=1', 'libelf debug=2', 'libelf debug=2 foo=1'
)
check_constrain('libelf+debug~foo', 'libelf+debug', 'libelf~foo')
check_constrain(
'libelf+debug~foo', 'libelf+debug', 'libelf+debug~foo'
)
[docs] def test_constrain_multi_value_variant(self):
check_constrain(
'multivalue-variant foo="bar,baz"',
'multivalue-variant foo="bar"',
'multivalue-variant foo="baz"'
)
check_constrain(
'multivalue-variant foo="bar,baz,barbaz"',
'multivalue-variant foo="bar,barbaz"',
'multivalue-variant foo="baz"'
)
check_constrain(
'libelf foo=bar,baz', 'libelf foo=bar,baz', 'libelf foo=*')
check_constrain(
'libelf foo=bar,baz', 'libelf foo=*', 'libelf foo=bar,baz')
[docs] def test_constrain_compiler_flags(self):
check_constrain(
'libelf cflags="-O3" cppflags="-Wall"',
'libelf cflags="-O3"',
'libelf cppflags="-Wall"'
)
check_constrain(
'libelf cflags="-O3" cppflags="-Wall"',
'libelf cflags="-O3"',
'libelf cflags="-O3" cppflags="-Wall"'
)
[docs] def test_constrain_architecture(self):
check_constrain(
'libelf target=default_target os=default_os',
'libelf target=default_target os=default_os',
'libelf target=default_target os=default_os'
)
check_constrain(
'libelf target=default_target os=default_os',
'libelf',
'libelf target=default_target os=default_os'
)
[docs] def test_constrain_compiler(self):
check_constrain(
'libelf %gcc@4.4.7', 'libelf %gcc@4.4.7', 'libelf %gcc@4.4.7'
)
check_constrain(
'libelf %gcc@4.4.7', 'libelf', 'libelf %gcc@4.4.7'
)
[docs] def test_invalid_constraint(self):
check_invalid_constraint('libelf@0:2.0', 'libelf@2.1:3')
check_invalid_constraint(
'libelf@0:2.5%gcc@4.8:4.9', 'libelf@2.1:3%gcc@4.5:4.7')
check_invalid_constraint('libelf+debug', 'libelf~debug')
check_invalid_constraint('libelf+debug~foo', 'libelf+debug+foo')
check_invalid_constraint('libelf debug=True', 'libelf debug=False')
check_invalid_constraint(
'libelf cppflags="-O3"', 'libelf cppflags="-O2"')
check_invalid_constraint(
'libelf platform=test target=be os=be', 'libelf target=fe os=fe'
)
check_invalid_constraint('libdwarf', '^%gcc')
[docs] def test_constrain_changed(self):
check_constrain_changed('libelf', '@1.0')
check_constrain_changed('libelf', '@1.0:5.0')
check_constrain_changed('libelf', '%gcc')
check_constrain_changed('libelf%gcc', '%gcc@4.5')
check_constrain_changed('libelf', '+debug')
check_constrain_changed('libelf', 'debug=*')
check_constrain_changed('libelf', '~debug')
check_constrain_changed('libelf', 'debug=2')
check_constrain_changed('libelf', 'cppflags="-O3"')
platform = spack.platforms.host()
check_constrain_changed(
'libelf', 'target=' + platform.target('default_target').name)
check_constrain_changed(
'libelf', 'os=' + platform.operating_system('default_os').name)
[docs] def test_constrain_not_changed(self):
check_constrain_not_changed('libelf', 'libelf')
check_constrain_not_changed('libelf@1.0', '@1.0')
check_constrain_not_changed('libelf@1.0:5.0', '@1.0:5.0')
check_constrain_not_changed('libelf%gcc', '%gcc')
check_constrain_not_changed('libelf%gcc@4.5', '%gcc@4.5')
check_constrain_not_changed('libelf+debug', '+debug')
check_constrain_not_changed('libelf~debug', '~debug')
check_constrain_not_changed('libelf debug=2', 'debug=2')
check_constrain_not_changed('libelf debug=2', 'debug=*')
check_constrain_not_changed(
'libelf cppflags="-O3"', 'cppflags="-O3"')
platform = spack.platforms.host()
default_target = platform.target('default_target').name
check_constrain_not_changed(
'libelf target=' + default_target, 'target=' + default_target)
[docs] def test_constrain_dependency_changed(self):
check_constrain_changed('libelf^foo', 'libelf^foo@1.0')
check_constrain_changed('libelf^foo', 'libelf^foo@1.0:5.0')
check_constrain_changed('libelf^foo', 'libelf^foo%gcc')
check_constrain_changed('libelf^foo%gcc', 'libelf^foo%gcc@4.5')
check_constrain_changed('libelf^foo', 'libelf^foo+debug')
check_constrain_changed('libelf^foo', 'libelf^foo~debug')
check_constrain_changed('libelf', '^foo')
platform = spack.platforms.host()
default_target = platform.target('default_target').name
check_constrain_changed(
'libelf^foo', 'libelf^foo target=' + default_target)
[docs] def test_constrain_dependency_not_changed(self):
check_constrain_not_changed('libelf^foo@1.0', 'libelf^foo@1.0')
check_constrain_not_changed(
'libelf^foo@1.0:5.0', 'libelf^foo@1.0:5.0')
check_constrain_not_changed('libelf^foo%gcc', 'libelf^foo%gcc')
check_constrain_not_changed(
'libelf^foo%gcc@4.5', 'libelf^foo%gcc@4.5')
check_constrain_not_changed(
'libelf^foo+debug', 'libelf^foo+debug')
check_constrain_not_changed(
'libelf^foo~debug', 'libelf^foo~debug')
check_constrain_not_changed(
'libelf^foo cppflags="-O3"', 'libelf^foo cppflags="-O3"')
platform = spack.platforms.host()
default_target = platform.target('default_target').name
check_constrain_not_changed(
'libelf^foo target=' + default_target,
'libelf^foo target=' + default_target)
[docs] def test_exceptional_paths_for_constructor(self):
with pytest.raises(TypeError):
Spec((1, 2))
with pytest.raises(ValueError):
Spec('')
with pytest.raises(ValueError):
Spec('libelf foo')
[docs] @pytest.mark.regression('9908')
def test_spec_flags_maintain_order(self):
# Spack was assembling flags in a manner that could result in
# different orderings for repeated concretizations of the same
# spec and config
spec_str = 'libelf %gcc@4.7.2 os=redhat6'
for _ in range(25):
s = Spec(spec_str).concretized()
assert all(
s.compiler_flags[x] == ['-O0', '-g']
for x in ('cflags', 'cxxflags', 'fflags')
)
[docs] def test_combination_of_wildcard_or_none(self):
# Test that using 'none' and another value raises
with pytest.raises(spack.variant.InvalidVariantValueCombinationError):
Spec('multivalue-variant foo=none,bar')
# Test that using wildcard and another value raises
with pytest.raises(spack.variant.InvalidVariantValueCombinationError):
Spec('multivalue-variant foo=*,bar')
[docs] @pytest.mark.skipif(
sys.version_info[0] == 2, reason='__wrapped__ requires python 3'
)
def test_errors_in_variant_directive(self):
variant = spack.directives.variant.__wrapped__
class Pkg(object):
name = 'PKG'
# We can't use names that are reserved by Spack
fn = variant('patches')
with pytest.raises(spack.directives.DirectiveError) as exc_info:
fn(Pkg())
assert "The name 'patches' is reserved" in str(exc_info.value)
# We can't have conflicting definitions for arguments
fn = variant(
'foo', values=spack.variant.any_combination_of('fee', 'foom'),
default='bar'
)
with pytest.raises(spack.directives.DirectiveError) as exc_info:
fn(Pkg())
assert " it is handled by an attribute of the 'values' " \
"argument" in str(exc_info.value)
# We can't leave None as a default value
fn = variant('foo', default=None)
with pytest.raises(spack.directives.DirectiveError) as exc_info:
fn(Pkg())
assert "either a default was not explicitly set, or 'None' was used"\
in str(exc_info.value)
# We can't use an empty string as a default value
fn = variant('foo', default='')
with pytest.raises(spack.directives.DirectiveError) as exc_info:
fn(Pkg())
assert "the default cannot be an empty string" in str(exc_info.value)
[docs] def test_abstract_spec_prefix_error(self):
spec = Spec('libelf')
with pytest.raises(SpecError):
spec.prefix
[docs] def test_forwarding_of_architecture_attributes(self):
spec = Spec('libelf target=x86_64').concretized()
# Check that we can still access each member through
# the architecture attribute
assert 'test' in spec.architecture
assert 'debian' in spec.architecture
assert 'x86_64' in spec.architecture
# Check that we forward the platform and os attribute correctly
assert spec.platform == 'test'
assert spec.os == 'debian6'
# Check that the target is also forwarded correctly and supports
# all the operators we expect
assert spec.target == 'x86_64'
assert spec.target.family == 'x86_64'
assert 'avx512' not in spec.target
assert spec.target < 'broadwell'
[docs] @pytest.mark.parametrize('transitive', [True, False])
def test_splice(self, transitive):
# Tests the new splice function in Spec using a somewhat simple case
# with a variant with a conditional dependency.
spec = Spec('splice-t')
dep = Spec('splice-h+foo')
spec.concretize()
dep.concretize()
# Sanity checking that these are not the same thing.
assert dep.dag_hash() != spec['splice-h'].dag_hash()
# Do the splice.
out = spec.splice(dep, transitive)
# Returned spec should still be concrete.
assert out.concrete
# Traverse the spec and assert that all dependencies are accounted for.
for node in spec.traverse():
assert node.name in out
# If the splice worked, then the dag hash of the spliced dep should
# now match the dag hash of the build spec of the dependency from the
# returned spec.
out_h_build = out['splice-h'].build_spec
assert out_h_build.dag_hash() == dep.dag_hash()
# Transitivity should determine whether the transitive dependency was
# changed.
expected_z = dep['splice-z'] if transitive else spec['splice-z']
assert out['splice-z'].dag_hash() == expected_z.dag_hash()
# Sanity check build spec of out should be the original spec.
assert (out['splice-t'].build_spec.dag_hash() ==
spec['splice-t'].dag_hash())
# Finally, the spec should know it's been spliced:
assert out.spliced
[docs] @pytest.mark.parametrize('transitive', [True, False])
def test_splice_with_cached_hashes(self, transitive):
spec = Spec('splice-t')
dep = Spec('splice-h+foo')
spec.concretize()
dep.concretize()
# monkeypatch hashes so we can test that they are cached
spec._hash = 'aaaaaa'
dep._hash = 'bbbbbb'
spec['splice-h']._hash = 'cccccc'
spec['splice-z']._hash = 'dddddd'
dep['splice-z']._hash = 'eeeeee'
out = spec.splice(dep, transitive=transitive)
out_z_expected = (dep if transitive else spec)['splice-z']
assert out.dag_hash() != spec.dag_hash()
assert (out['splice-h'].dag_hash() == dep.dag_hash()) == transitive
assert out['splice-z'].dag_hash() == out_z_expected.dag_hash()
[docs] @pytest.mark.parametrize('transitive', [True, False])
def test_splice_subsequent(self, transitive):
spec = Spec('splice-t')
dep = Spec('splice-h+foo')
spec.concretize()
dep.concretize()
out = spec.splice(dep, transitive)
# Now we attempt a second splice.
dep = Spec('splice-z+bar')
dep.concretize()
# Transitivity shouldn't matter since Splice Z has no dependencies.
out2 = out.splice(dep, transitive)
assert out2.concrete
assert out2['splice-z'].dag_hash() != spec['splice-z'].dag_hash()
assert out2['splice-z'].dag_hash() != out['splice-z'].dag_hash()
assert (out2['splice-t'].build_spec.dag_hash() ==
spec['splice-t'].dag_hash())
assert out2.spliced
[docs] @pytest.mark.parametrize('transitive', [True, False])
def test_splice_dict(self, transitive):
spec = Spec('splice-t')
dep = Spec('splice-h+foo')
spec.concretize()
dep.concretize()
out = spec.splice(dep, transitive)
# Sanity check all hashes are unique...
assert spec.dag_hash() != dep.dag_hash()
assert out.dag_hash() != dep.dag_hash()
assert out.dag_hash() != spec.dag_hash()
node_list = out.to_dict()['spec']['nodes']
root_nodes = [n for n in node_list if n['hash'] == out.dag_hash()]
build_spec_nodes = [n for n in node_list if n['hash'] == spec.dag_hash()]
assert spec.dag_hash() == out.build_spec.dag_hash()
assert len(root_nodes) == 1
assert len(build_spec_nodes) == 1
[docs] @pytest.mark.parametrize('transitive', [True, False])
def test_splice_dict_roundtrip(self, transitive):
spec = Spec('splice-t')
dep = Spec('splice-h+foo')
spec.concretize()
dep.concretize()
out = spec.splice(dep, transitive)
# Sanity check all hashes are unique...
assert spec.dag_hash() != dep.dag_hash()
assert out.dag_hash() != dep.dag_hash()
assert out.dag_hash() != spec.dag_hash()
out_rt_spec = Spec.from_dict(out.to_dict()) # rt is "round trip"
assert out_rt_spec.dag_hash() == out.dag_hash()
out_rt_spec_bld_hash = out_rt_spec.build_spec.dag_hash()
out_rt_spec_h_bld_hash = out_rt_spec['splice-h'].build_spec.dag_hash()
out_rt_spec_z_bld_hash = out_rt_spec['splice-z'].build_spec.dag_hash()
# In any case, the build spec for splice-t (root) should point to the
# original spec, preserving build provenance.
assert spec.dag_hash() == out_rt_spec_bld_hash
assert out_rt_spec.dag_hash() != out_rt_spec_bld_hash
# The build spec for splice-h should always point to the introduced
# spec, since that is the spec spliced in.
assert dep['splice-h'].dag_hash() == out_rt_spec_h_bld_hash
# The build spec for splice-z will depend on whether or not the splice
# was transitive.
expected_z_bld_hash = (dep['splice-z'].dag_hash() if transitive else
spec['splice-z'].dag_hash())
assert expected_z_bld_hash == out_rt_spec_z_bld_hash
[docs] @pytest.mark.parametrize('spec,constraint,expected_result', [
('libelf target=haswell', 'target=broadwell', False),
('libelf target=haswell', 'target=haswell', True),
('libelf target=haswell', 'target=x86_64:', True),
('libelf target=haswell', 'target=:haswell', True),
('libelf target=haswell', 'target=icelake,:nocona', False),
('libelf target=haswell', 'target=haswell,:nocona', True),
# Check that a single target is not treated as the start
# or the end of an open range
('libelf target=haswell', 'target=x86_64', False),
('libelf target=x86_64', 'target=haswell', False),
])
@pytest.mark.regression('13111')
def test_target_constraints(self, spec, constraint, expected_result):
s = Spec(spec)
assert s.satisfies(constraint) is expected_result
[docs] @pytest.mark.regression('13124')
def test_error_message_unknown_variant(self):
s = Spec('mpileaks +unknown')
with pytest.raises(UnknownVariantError, match=r'package has no such'):
s.concretize()
[docs] @pytest.mark.regression('18527')
def test_satisfies_dependencies_ordered(self):
d = Spec('zmpi ^fake')
s = Spec('mpileaks')
s._add_dependency(d, ())
assert s.satisfies('mpileaks ^zmpi ^fake', strict=True)
[docs] @pytest.mark.parametrize('transitive', [True, False])
def test_splice_swap_names(self, transitive):
spec = Spec('splice-t')
dep = Spec('splice-a+foo')
spec.concretize()
dep.concretize()
out = spec.splice(dep, transitive)
assert dep.name in out
assert transitive == ('+foo' in out['splice-z'])
[docs] @pytest.mark.parametrize('transitive', [True, False])
def test_splice_swap_names_mismatch_virtuals(self, transitive):
spec = Spec('splice-t')
dep = Spec('splice-vh+foo')
spec.concretize()
dep.concretize()
with pytest.raises(spack.spec.SpliceError,
match='will not provide the same virtuals.'):
spec.splice(dep, transitive)
[docs]@pytest.mark.regression('3887')
@pytest.mark.parametrize('spec_str', [
'git', 'hdf5', 'py-flake8'
])
def test_is_extension_after_round_trip_to_dict(config, spec_str):
# x is constructed directly from string, y from a
# round-trip to dict representation
x = Spec(spec_str)
x.concretize()
y = Spec.from_dict(x.to_dict())
# Using 'y' since the round-trip make us lose build dependencies
for d in y.traverse():
assert x[d.name].package.is_extension == y[d.name].package.is_extension
[docs]def test_spec_dict_hashless_dep():
with pytest.raises(SpecError, match="Couldn't parse"):
Spec.from_dict(
{
'spec': {
'nodes': [
{
'name': 'foo',
'hash': 'thehash',
'dependencies': [
{
'name': 'bar'
}
]
}
]
}
}
)
[docs]@pytest.mark.parametrize('specs,expected', [
# Anonymous specs without dependencies
(['+baz', '+bar'], '+baz+bar'),
(['@2.0:', '@:5.1', '+bar'], '@2.0:5.1 +bar'),
# Anonymous specs with dependencies
(['^mpich@3.2', '^mpich@:4.0+foo'], '^mpich@3.2 +foo'),
# Mix a real package with a virtual one. This test
# should fail if we start using the repository
(['^mpich@3.2', '^mpi+foo'], '^mpich@3.2 ^mpi+foo'),
])
def test_merge_abstract_anonymous_specs(specs, expected):
specs = [Spec(x) for x in specs]
result = spack.spec.merge_abstract_anonymous_specs(*specs)
assert result == Spec(expected)
[docs]@pytest.mark.parametrize('anonymous,named,expected', [
('+plumed', 'gromacs', 'gromacs+plumed'),
('+plumed ^plumed%gcc', 'gromacs', 'gromacs+plumed ^plumed%gcc'),
('+plumed', 'builtin.gromacs', 'builtin.gromacs+plumed')
])
def test_merge_anonymous_spec_with_named_spec(anonymous, named, expected):
s = Spec(anonymous)
changed = s.constrain(named)
assert changed
assert s == Spec(expected)
[docs]def test_spec_installed(install_mockery, database):
"""Test whether Spec.installed works."""
# a known installed spec should say that it's installed
specs = database.query()
spec = specs[0]
assert spec.installed
assert spec.copy().installed
# an abstract spec should say it's not installed
spec = Spec("not-a-real-package")
assert not spec.installed
# 'a' is not in the mock DB and is not installed
spec = Spec("a").concretized()
assert not spec.installed
[docs]@pytest.mark.regression('30678')
def test_call_dag_hash_on_old_dag_hash_spec(mock_packages, config):
# create a concrete spec
a = Spec("a").concretized()
dag_hashes = {
spec.name: spec.dag_hash() for spec in a.traverse()
}
# make it look like an old DAG hash spec with no package hash on the spec.
for spec in a.traverse():
assert spec.concrete
spec._package_hash = None
for spec in a.traverse():
assert dag_hashes[spec.name] == spec.dag_hash()
with pytest.raises(ValueError, match='Cannot call package_hash()'):
spec.package_hash()
[docs]@pytest.mark.regression('30861')
def test_concretize_partial_old_dag_hash_spec(mock_packages, config):
# create an "old" spec with no package hash
bottom = Spec("dt-diamond-bottom").concretized()
delattr(bottom, "_package_hash")
dummy_hash = "zd4m26eis2wwbvtyfiliar27wkcv3ehk"
bottom._hash = dummy_hash
# add it to an abstract spec as a dependency
top = Spec("dt-diamond")
top.add_dependency_edge(bottom, ())
# concretize with the already-concrete dependency
top.concretize()
for spec in top.traverse():
assert spec.concrete
# make sure dag_hash is untouched
assert spec["dt-diamond-bottom"].dag_hash() == dummy_hash
assert spec["dt-diamond-bottom"]._hash == dummy_hash
# make sure package hash is NOT recomputed
assert not getattr(spec["dt-diamond-bottom"], '_package_hash', None)
[docs]def test_unsupported_compiler():
with pytest.raises(UnsupportedCompilerError):
Spec('gcc%fake-compiler').validate_or_raise()