summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorGreg Becker <becker33@llnl.gov>2020-06-02 02:02:28 -0700
committerGitHub <noreply@github.com>2020-06-02 11:02:28 +0200
commit2795414a80839208c3463e49f197fee25f2be0f8 (patch)
treef06df81c12247a8717209b9fe2adf1241674831b /lib
parent0875c6a5d0ba8a951f86805814d4eb0ba3f3997e (diff)
downloadspack-2795414a80839208c3463e49f197fee25f2be0f8.tar.gz
spack-2795414a80839208c3463e49f197fee25f2be0f8.tar.bz2
spack-2795414a80839208c3463e49f197fee25f2be0f8.tar.xz
spack-2795414a80839208c3463e49f197fee25f2be0f8.zip
Fix satisfaction checks for excluding variants from matrices (#16893)
Because of the way abstract variants are implemented, the following spec matrix does not work as intended: ``` matrix: - [foo] - [bar=a, bar=b] exclude: - bar=a ``` because abstract variants always satisfy any variant of the same name, regardless of values. This PR converts abstract variants to whatever their appropriate type is before running satisfaction checks for the excludes clause in a matrix. fixes #16841
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/spack/spec_list.py12
-rw-r--r--lib/spack/spack/test/cmd/dependents.py10
-rw-r--r--lib/spack/spack/test/spec_list.py7
-rw-r--r--lib/spack/spack/test/spec_semantics.py84
-rw-r--r--lib/spack/spack/test/spec_yaml.py2
-rw-r--r--lib/spack/spack/variant.py13
6 files changed, 79 insertions, 49 deletions
diff --git a/lib/spack/spack/spec_list.py b/lib/spack/spack/spec_list.py
index de75458f32..4468df7c35 100644
--- a/lib/spack/spack/spec_list.py
+++ b/lib/spack/spack/spec_list.py
@@ -5,6 +5,7 @@
import itertools
from six import string_types
+import spack.variant
from spack.spec import Spec
from spack.error import SpackError
@@ -189,7 +190,18 @@ def _expand_matrix_constraints(object, specify=True):
# Construct a combined spec to test against excludes
flat_combo = [constraint for list in combo for constraint in list]
ordered_combo = sorted(flat_combo, key=spec_ordering_key)
+
test_spec = Spec(' '.join(ordered_combo))
+ # Abstract variants don't have normal satisfaction semantics
+ # Convert all variants to concrete types.
+ # This method is best effort, so all existing variants will be
+ # converted before any error is raised.
+ # Catch exceptions because we want to be able to operate on
+ # abstract specs without needing package information
+ try:
+ spack.variant.substitute_abstract_variants(test_spec)
+ except spack.variant.UnknownVariantError:
+ pass
if any(test_spec.satisfies(x) for x in excludes):
continue
diff --git a/lib/spack/spack/test/cmd/dependents.py b/lib/spack/spack/test/cmd/dependents.py
index 1001e8764f..22a3acd0c3 100644
--- a/lib/spack/spack/test/cmd/dependents.py
+++ b/lib/spack/spack/test/cmd/dependents.py
@@ -26,7 +26,7 @@ def test_transitive_dependents(mock_packages):
out = dependents('--transitive', 'libelf')
actual = set(re.split(r'\s+', out.strip()))
assert actual == set(
- ['callpath', 'dyninst', 'libdwarf', 'mpileaks', 'multivalue_variant',
+ ['callpath', 'dyninst', 'libdwarf', 'mpileaks', 'multivalue-variant',
'singlevalue-variant-dependent',
'patch-a-dependency', 'patch-several-dependencies'])
@@ -36,8 +36,8 @@ def test_immediate_installed_dependents(mock_packages, database):
with color_when(False):
out = dependents('--installed', 'libelf')
- lines = [l for l in out.strip().split('\n') if not l.startswith('--')]
- hashes = set([re.split(r'\s+', l)[0] for l in lines])
+ lines = [li for li in out.strip().split('\n') if not li.startswith('--')]
+ hashes = set([re.split(r'\s+', li)[0] for li in lines])
expected = set([spack.store.db.query_one(s).dag_hash(7)
for s in ['dyninst', 'libdwarf']])
@@ -53,8 +53,8 @@ def test_transitive_installed_dependents(mock_packages, database):
with color_when(False):
out = dependents('--installed', '--transitive', 'fake')
- lines = [l for l in out.strip().split('\n') if not l.startswith('--')]
- hashes = set([re.split(r'\s+', l)[0] for l in lines])
+ lines = [li for li in out.strip().split('\n') if not li.startswith('--')]
+ hashes = set([re.split(r'\s+', li)[0] for li in lines])
expected = set([spack.store.db.query_one(s).dag_hash(7)
for s in ['zmpi', 'callpath^zmpi', 'mpileaks^zmpi']])
diff --git a/lib/spack/spack/test/spec_list.py b/lib/spack/spack/test/spec_list.py
index 9bbbc435e2..ff45096c3e 100644
--- a/lib/spack/spack/test/spec_list.py
+++ b/lib/spack/spack/test/spec_list.py
@@ -156,3 +156,10 @@ class TestSpecList(object):
['+shared', '~shared'])
expected = [Spec(' '.join(combo)) for combo in expected_components]
assert set(speclist.specs) == set(expected)
+
+ def test_spec_list_matrix_exclude(self, mock_packages):
+ # Test on non-boolean variants for regression for #16841
+ matrix = [{'matrix': [['multivalue-variant'], ['foo=bar', 'foo=baz']],
+ 'exclude': ['foo=bar']}]
+ speclist = SpecList('specs', matrix)
+ assert len(speclist.specs) == 1
diff --git a/lib/spack/spack/test/spec_semantics.py b/lib/spack/spack/test/spec_semantics.py
index b55fa27ae1..d908ce7d89 100644
--- a/lib/spack/spack/test/spec_semantics.py
+++ b/lib/spack/spack/test/spec_semantics.py
@@ -275,27 +275,27 @@ class TestSpecSematics(object):
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')
+ 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="bar"')
+ 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"',
+ 'multivalue-variant foo="baz"')
- check_satisfies('multivalue_variant foo="bar,baz,barbaz"',
- 'multivalue_variant foo="bar,baz"')
+ check_satisfies('multivalue-variant foo="bar,baz,barbaz"',
+ 'multivalue-variant foo="bar,baz"')
- check_satisfies('multivalue_variant foo="bar,baz"',
+ check_satisfies('multivalue-variant foo="bar,baz"',
'foo="bar,baz"')
- check_satisfies('multivalue_variant foo="bar,baz"',
+ check_satisfies('multivalue-variant foo="bar,baz"',
'foo="bar"')
def test_satisfies_single_valued_variant(self):
@@ -325,7 +325,7 @@ class TestSpecSematics(object):
a.concretize()
assert '^b' not in a
- mv = Spec('multivalue_variant')
+ mv = Spec('multivalue-variant')
mv.concretize()
assert 'a@1.0' not in mv
@@ -340,9 +340,9 @@ class TestSpecSematics(object):
# Depending on whether the spec is concrete or not
a = make_spec(
- 'multivalue_variant foo="bar"', concrete=True
+ 'multivalue-variant foo="bar"', concrete=True
)
- spec_str = 'multivalue_variant foo="bar,baz"'
+ spec_str = 'multivalue-variant foo="bar,baz"'
b = Spec(spec_str)
assert not a.satisfies(b)
assert not a.satisfies(spec_str)
@@ -350,8 +350,8 @@ class TestSpecSematics(object):
with pytest.raises(UnsatisfiableSpecError):
a.constrain(b)
- a = Spec('multivalue_variant foo="bar"')
- spec_str = 'multivalue_variant foo="bar,baz"'
+ 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)
@@ -360,9 +360,9 @@ class TestSpecSematics(object):
assert a.constrain(b)
a = make_spec(
- 'multivalue_variant foo="bar,baz"', concrete=True
+ 'multivalue-variant foo="bar,baz"', concrete=True
)
- spec_str = 'multivalue_variant foo="bar,baz,quux"'
+ spec_str = 'multivalue-variant foo="bar,baz,quux"'
b = Spec(spec_str)
assert not a.satisfies(b)
assert not a.satisfies(spec_str)
@@ -370,8 +370,8 @@ class TestSpecSematics(object):
with pytest.raises(UnsatisfiableSpecError):
a.constrain(b)
- a = Spec('multivalue_variant foo="bar,baz"')
- spec_str = 'multivalue_variant foo="bar,baz,quux"'
+ 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)
@@ -384,8 +384,8 @@ class TestSpecSematics(object):
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"'
+ 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
@@ -405,20 +405,20 @@ class TestSpecSematics(object):
# 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('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_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_spec='multivalue-variant foo="bar"',
+ constraint_spec='multivalue-variant ~foo',
target_concrete=True
)
@@ -597,15 +597,15 @@ class TestSpecSematics(object):
def test_constrain_multi_value_variant(self):
check_constrain(
- 'multivalue_variant foo="bar,baz"',
- 'multivalue_variant foo="bar"',
- 'multivalue_variant foo="baz"'
+ '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"'
+ 'multivalue-variant foo="bar,baz,barbaz"',
+ 'multivalue-variant foo="bar,barbaz"',
+ 'multivalue-variant foo="baz"'
)
def test_constrain_compiler_flags(self):
@@ -734,7 +734,7 @@ class TestSpecSematics(object):
Spec('libelf foo')
def test_spec_formatting(self):
- spec = Spec("multivalue_variant cflags=-O2")
+ spec = Spec("multivalue-variant cflags=-O2")
spec.concretize()
# Since the default is the full spec see if the string rep of
@@ -806,7 +806,7 @@ class TestSpecSematics(object):
assert expected == actual
def test_spec_formatting_escapes(self):
- spec = Spec('multivalue_variant cflags=-O2')
+ spec = Spec('multivalue-variant cflags=-O2')
spec.concretize()
sigil_mismatches = [
@@ -895,7 +895,7 @@ class TestSpecSematics(object):
def test_any_combination_of(self):
# Test that using 'none' and another value raise during concretization
- spec = Spec('multivalue_variant foo=none,bar')
+ spec = Spec('multivalue-variant foo=none,bar')
with pytest.raises(spack.error.SpecError) as exc_info:
spec.concretize()
diff --git a/lib/spack/spack/test/spec_yaml.py b/lib/spack/spack/test/spec_yaml.py
index 03129a7eb9..98fb1e68fe 100644
--- a/lib/spack/spack/test/spec_yaml.py
+++ b/lib/spack/spack/test/spec_yaml.py
@@ -69,7 +69,7 @@ def test_concrete_spec(config, mock_packages):
def test_yaml_multivalue(config, mock_packages):
- spec = Spec('multivalue_variant foo="bar,baz"')
+ spec = Spec('multivalue-variant foo="bar,baz"')
spec.concretize()
check_yaml_round_trip(spec)
diff --git a/lib/spack/spack/variant.py b/lib/spack/spack/variant.py
index 0443b68ec3..e43a002182 100644
--- a/lib/spack/spack/variant.py
+++ b/lib/spack/spack/variant.py
@@ -593,19 +593,30 @@ def substitute_abstract_variants(spec):
"""Uses the information in `spec.package` to turn any variant that needs
it into a SingleValuedVariant.
+ This method is best effort. All variants that can be substituted will be
+ substituted before any error is raised.
+
Args:
spec: spec on which to operate the substitution
"""
+ # This method needs to be best effort so that it works in matrix exlusion
+ # in $spack/lib/spack/spack/spec_list.py
+ failed = []
for name, v in spec.variants.items():
if name in spack.directives.reserved_names:
continue
pkg_variant = spec.package_class.variants.get(name, None)
if not pkg_variant:
- raise UnknownVariantError(spec, [name])
+ failed.append(name)
+ continue
new_variant = pkg_variant.make_variant(v._original_value)
pkg_variant.validate_or_raise(new_variant, spec.package_class)
spec.variants.substitute(new_variant)
+ # Raise all errors at once
+ if failed:
+ raise UnknownVariantError(spec, failed)
+
# The class below inherit from Sequence to disguise as a tuple and comply
# with the semantic expected by the 'values' argument of the variant directive