Fix for SPACK-13, and satisfies() now handles deps.
Added more test cases for multimethods. In doing so, (re)discovered that satisfies() really needs to handle dependencies properly. Implemented support for dependencies in satisfies, but constrain() now isn't consistent (as we do not currently constrain deps), so need to implement that. Virtual dependency support probably needs some deeper thought. i.e., there is probably an intermediate DAG form that would make the needed checks easier. Right now we have to build ProviderIndexes to figure out how virtual dependencies are set up. If the vdep were preserved in the DAG, then we could just check for things like incompatible providers directly.
This commit is contained in:
parent
f7706d231d
commit
7088cdf25f
11 changed files with 419 additions and 121 deletions
|
@ -11,6 +11,10 @@ def supported_compilers():
|
||||||
return [c for c in list_modules(spack.compilers_path)]
|
return [c for c in list_modules(spack.compilers_path)]
|
||||||
|
|
||||||
|
|
||||||
|
def supported(compiler):
|
||||||
|
return compiler in supported_compilers()
|
||||||
|
|
||||||
|
|
||||||
@memoized
|
@memoized
|
||||||
def default_compiler():
|
def default_compiler():
|
||||||
from spack.spec import Compiler
|
from spack.spec import Compiler
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
import spack.architecture
|
import spack.architecture
|
||||||
import spack.error
|
import spack.error
|
||||||
from spack.util.lang import *
|
from spack.util.lang import *
|
||||||
from spack.spec import parse_local_spec
|
from spack.spec import parse_local_spec, Spec
|
||||||
|
|
||||||
|
|
||||||
class SpecMultiMethod(object):
|
class SpecMultiMethod(object):
|
||||||
|
@ -94,19 +94,37 @@ def __call__(self, package_self, *args, **kwargs):
|
||||||
spec = package_self.spec
|
spec = package_self.spec
|
||||||
matching_specs = [s for s in self.method_map if s.satisfies(spec)]
|
matching_specs = [s for s in self.method_map if s.satisfies(spec)]
|
||||||
|
|
||||||
if not matching_specs and self.default is None:
|
# from pprint import pprint
|
||||||
raise NoSuchMethodVersionError(type(package_self), self.__name__,
|
|
||||||
spec, self.method_map.keys())
|
# print "========"
|
||||||
elif len(matching_specs) > 1:
|
# print "called with " + str(spec)
|
||||||
raise AmbiguousMethodVersionError(type(package_self), self.__name__,
|
# print spec, matching_specs
|
||||||
|
# pprint(self.method_map)
|
||||||
|
# print "SATISFIES: ", [Spec('multimethod%gcc').satisfies(s) for s in self.method_map]
|
||||||
|
# print [spec.satisfies(s) for s in self.method_map]
|
||||||
|
# print
|
||||||
|
|
||||||
|
num_matches = len(matching_specs)
|
||||||
|
if num_matches == 0:
|
||||||
|
if self.default is None:
|
||||||
|
raise NoSuchMethodError(type(package_self), self.__name__,
|
||||||
|
spec, self.method_map.keys())
|
||||||
|
else:
|
||||||
|
method = self.default
|
||||||
|
|
||||||
|
elif num_matches == 1:
|
||||||
|
method = self.method_map[matching_specs[0]]
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise AmbiguousMethodError(type(package_self), self.__name__,
|
||||||
spec, matching_specs)
|
spec, matching_specs)
|
||||||
|
|
||||||
method = self.method_map[matching_specs[0]]
|
|
||||||
return method(package_self, *args, **kwargs)
|
return method(package_self, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "<%s, %s>" % (self.default, self.method_map)
|
return "SpecMultiMethod {\n\tdefault: %s,\n\tspecs: %s\n}" % (
|
||||||
|
self.default, self.method_map)
|
||||||
|
|
||||||
|
|
||||||
class when(object):
|
class when(object):
|
||||||
|
@ -193,19 +211,19 @@ def __init__(self, message):
|
||||||
super(MultiMethodError, self).__init__(message)
|
super(MultiMethodError, self).__init__(message)
|
||||||
|
|
||||||
|
|
||||||
class NoSuchMethodVersionError(spack.error.SpackError):
|
class NoSuchMethodError(spack.error.SpackError):
|
||||||
"""Raised when we can't find a version of a multi-method."""
|
"""Raised when we can't find a version of a multi-method."""
|
||||||
def __init__(self, cls, method_name, spec, possible_specs):
|
def __init__(self, cls, method_name, spec, possible_specs):
|
||||||
super(NoSuchMethodVersionError, self).__init__(
|
super(NoSuchMethodError, self).__init__(
|
||||||
"Package %s does not support %s called with %s. Options are: %s"
|
"Package %s does not support %s called with %s. Options are: %s"
|
||||||
% (cls.__name__, method_name, spec,
|
% (cls.__name__, method_name, spec,
|
||||||
", ".join(str(s) for s in possible_specs)))
|
", ".join(str(s) for s in possible_specs)))
|
||||||
|
|
||||||
|
|
||||||
class AmbiguousMethodVersionError(spack.error.SpackError):
|
class AmbiguousMethodError(spack.error.SpackError):
|
||||||
"""Raised when we can't find a version of a multi-method."""
|
"""Raised when we can't find a version of a multi-method."""
|
||||||
def __init__(self, cls, method_name, spec, matching_specs):
|
def __init__(self, cls, method_name, spec, matching_specs):
|
||||||
super(AmbiguousMethodVersionError, self).__init__(
|
super(AmbiguousMethodError, self).__init__(
|
||||||
"Package %s has multiple versions of %s that match %s: %s"
|
"Package %s has multiple versions of %s that match %s: %s"
|
||||||
% (cls.__name__, method_name, spec,
|
% (cls.__name__, method_name, spec,
|
||||||
",".join(str(s) for s in matching_specs)))
|
",".join(str(s) for s in matching_specs)))
|
||||||
|
|
|
@ -45,7 +45,7 @@ class ProviderIndex(object):
|
||||||
{ mpi@:1.1 : mpich,
|
{ mpi@:1.1 : mpich,
|
||||||
mpi@:2.3 : mpich2@1.9: } }
|
mpi@:2.3 : mpich2@1.9: } }
|
||||||
|
|
||||||
Calling find_provider(spec) will find a package that provides a
|
Calling providers_for(spec) will find specs that provide a
|
||||||
matching implementation of MPI.
|
matching implementation of MPI.
|
||||||
"""
|
"""
|
||||||
def __init__(self, specs, **kwargs):
|
def __init__(self, specs, **kwargs):
|
||||||
|
@ -61,7 +61,7 @@ def __init__(self, specs, **kwargs):
|
||||||
|
|
||||||
pkg = spec.package
|
pkg = spec.package
|
||||||
for provided_spec, provider_spec in pkg.provided.iteritems():
|
for provided_spec, provider_spec in pkg.provided.iteritems():
|
||||||
if provider_spec.satisfies(spec):
|
if provider_spec.satisfies(spec, deps=False):
|
||||||
provided_name = provided_spec.name
|
provided_name = provided_spec.name
|
||||||
if provided_name not in self.providers:
|
if provided_name not in self.providers:
|
||||||
self.providers[provided_name] = {}
|
self.providers[provided_name] = {}
|
||||||
|
@ -79,8 +79,8 @@ def __init__(self, specs, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
def providers_for(self, *vpkg_specs):
|
def providers_for(self, *vpkg_specs):
|
||||||
"""Gives names of all packages that provide virtual packages
|
"""Gives specs of all packages that provide virtual packages
|
||||||
with the supplied names."""
|
with the supplied specs."""
|
||||||
providers = set()
|
providers = set()
|
||||||
for vspec in vpkg_specs:
|
for vspec in vpkg_specs:
|
||||||
# Allow string names to be passed as input, as well as specs
|
# Allow string names to be passed as input, as well as specs
|
||||||
|
@ -90,13 +90,46 @@ def providers_for(self, *vpkg_specs):
|
||||||
# Add all the providers that satisfy the vpkg spec.
|
# Add all the providers that satisfy the vpkg spec.
|
||||||
if vspec.name in self.providers:
|
if vspec.name in self.providers:
|
||||||
for provider_spec, spec in self.providers[vspec.name].items():
|
for provider_spec, spec in self.providers[vspec.name].items():
|
||||||
if provider_spec.satisfies(vspec):
|
if provider_spec.satisfies(vspec, deps=False):
|
||||||
providers.add(spec)
|
providers.add(spec)
|
||||||
|
|
||||||
# Return providers in order
|
# Return providers in order
|
||||||
return sorted(providers)
|
return sorted(providers)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: this is pretty darned nasty, and inefficient.
|
||||||
|
def _cross_provider_maps(self, lmap, rmap):
|
||||||
|
result = {}
|
||||||
|
for lspec in lmap:
|
||||||
|
for rspec in rmap:
|
||||||
|
try:
|
||||||
|
constrained = lspec.copy().constrain(rspec)
|
||||||
|
if lmap[lspec].name != rmap[rspec].name:
|
||||||
|
continue
|
||||||
|
result[constrained] = lmap[lspec].copy().constrain(rmap[rspec])
|
||||||
|
except spack.spec.UnsatisfiableSpecError:
|
||||||
|
continue
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def satisfies(self, other):
|
||||||
|
"""Check that providers of virtual specs are compatible."""
|
||||||
|
common = set(self.providers.keys())
|
||||||
|
common.intersection_update(other.providers.keys())
|
||||||
|
|
||||||
|
if not common:
|
||||||
|
return True
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
for name in common:
|
||||||
|
crossed = self._cross_provider_maps(self.providers[name],
|
||||||
|
other.providers[name])
|
||||||
|
if crossed:
|
||||||
|
result[name] = crossed
|
||||||
|
|
||||||
|
return bool(result)
|
||||||
|
|
||||||
|
|
||||||
@autospec
|
@autospec
|
||||||
def get(spec):
|
def get(spec):
|
||||||
if spec.virtual:
|
if spec.virtual:
|
||||||
|
|
|
@ -67,15 +67,16 @@
|
||||||
expansion when it is the first character in an id typed on the command line.
|
expansion when it is the first character in an id typed on the command line.
|
||||||
"""
|
"""
|
||||||
import sys
|
import sys
|
||||||
|
import itertools
|
||||||
|
import hashlib
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
|
|
||||||
import tty
|
|
||||||
import hashlib
|
|
||||||
import spack.parse
|
import spack.parse
|
||||||
import spack.error
|
import spack.error
|
||||||
import spack.compilers
|
import spack.compilers
|
||||||
import spack.compilers.gcc
|
import spack.compilers.gcc
|
||||||
import spack.packages as packages
|
import spack.packages as packages
|
||||||
|
import spack.tty as tty
|
||||||
|
|
||||||
from spack.version import *
|
from spack.version import *
|
||||||
from spack.color import *
|
from spack.color import *
|
||||||
|
@ -102,7 +103,11 @@
|
||||||
'^' : dependency_color }
|
'^' : dependency_color }
|
||||||
|
|
||||||
"""Regex used for splitting by spec field separators."""
|
"""Regex used for splitting by spec field separators."""
|
||||||
separators = '[%s]' % ''.join(color_formats.keys())
|
_separators = '[%s]' % ''.join(color_formats.keys())
|
||||||
|
|
||||||
|
"""Versionlist constant so we don't have to build a list
|
||||||
|
every time we call str()"""
|
||||||
|
_any_version = VersionList([':'])
|
||||||
|
|
||||||
|
|
||||||
def index_specs(specs):
|
def index_specs(specs):
|
||||||
|
@ -134,7 +139,7 @@ def __call__(self, match):
|
||||||
|
|
||||||
return '%s%s' % (color_formats[sep], cescape(sep))
|
return '%s%s' % (color_formats[sep], cescape(sep))
|
||||||
|
|
||||||
return colorize(re.sub(separators, insert_color(), str(spec)) + '@.')
|
return colorize(re.sub(_separators, insert_color(), str(spec)) + '@.')
|
||||||
|
|
||||||
|
|
||||||
@key_ordering
|
@key_ordering
|
||||||
|
@ -143,9 +148,6 @@ class Compiler(object):
|
||||||
versions that a package should be built with. Compilers have a
|
versions that a package should be built with. Compilers have a
|
||||||
name and a version list. """
|
name and a version list. """
|
||||||
def __init__(self, name, version=None):
|
def __init__(self, name, version=None):
|
||||||
if name not in spack.compilers.supported_compilers():
|
|
||||||
raise UnknownCompilerError(name)
|
|
||||||
|
|
||||||
self.name = name
|
self.name = name
|
||||||
self.versions = VersionList()
|
self.versions = VersionList()
|
||||||
if version:
|
if version:
|
||||||
|
@ -193,7 +195,7 @@ def _cmp_key(self):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
out = self.name
|
out = self.name
|
||||||
if self.versions:
|
if self.versions and self.versions != _any_version:
|
||||||
vlist = ",".join(str(v) for v in self.versions)
|
vlist = ",".join(str(v) for v in self.versions)
|
||||||
out += "@%s" % vlist
|
out += "@%s" % vlist
|
||||||
return out
|
return out
|
||||||
|
@ -242,11 +244,6 @@ def concrete(self):
|
||||||
return all(d.concrete for d in self.values())
|
return all(d.concrete for d in self.values())
|
||||||
|
|
||||||
|
|
||||||
def satisfies(self, other):
|
|
||||||
return all(self[name].satisfies(other[name]) for name in self
|
|
||||||
if name in other)
|
|
||||||
|
|
||||||
|
|
||||||
def sha1(self):
|
def sha1(self):
|
||||||
sha = hashlib.sha1()
|
sha = hashlib.sha1()
|
||||||
sha.update(str(self))
|
sha.update(str(self))
|
||||||
|
@ -656,8 +653,8 @@ def normalize(self):
|
||||||
TODO: normalize should probably implement some form of cycle detection,
|
TODO: normalize should probably implement some form of cycle detection,
|
||||||
to ensure that the spec is actually a DAG.
|
to ensure that the spec is actually a DAG.
|
||||||
"""
|
"""
|
||||||
# Ensure first that all packages in the DAG exist.
|
# Ensure first that all packages & compilers in the DAG exist.
|
||||||
self.validate_package_names()
|
self.validate_names()
|
||||||
|
|
||||||
# Then ensure that the packages referenced are sane, that the
|
# Then ensure that the packages referenced are sane, that the
|
||||||
# provided spec is sane, and that all dependency specs are in the
|
# provided spec is sane, and that all dependency specs are in the
|
||||||
|
@ -689,12 +686,29 @@ def normalize(self):
|
||||||
self.name + " does not depend on " + comma_or(extra))
|
self.name + " does not depend on " + comma_or(extra))
|
||||||
|
|
||||||
|
|
||||||
def validate_package_names(self):
|
def normalized(self):
|
||||||
|
"""Return a normalized copy of this spec without modifying this spec."""
|
||||||
|
clone = self.copy()
|
||||||
|
clone.normalized()
|
||||||
|
return clone
|
||||||
|
|
||||||
|
|
||||||
|
def validate_names(self):
|
||||||
|
"""This checks that names of packages and compilers in this spec are real.
|
||||||
|
If they're not, it will raise either UnknownPackageError or
|
||||||
|
UnknownCompilerError.
|
||||||
|
"""
|
||||||
for spec in self.preorder_traversal():
|
for spec in self.preorder_traversal():
|
||||||
# Don't get a package for a virtual name.
|
# Don't get a package for a virtual name.
|
||||||
if not spec.virtual:
|
if not spec.virtual:
|
||||||
packages.get(spec.name)
|
packages.get(spec.name)
|
||||||
|
|
||||||
|
# validate compiler name in addition to the package name.
|
||||||
|
if spec.compiler:
|
||||||
|
compiler_name = spec.compiler.name
|
||||||
|
if not spack.compilers.supported(compiler_name):
|
||||||
|
raise UnknownCompilerError(compiler_name)
|
||||||
|
|
||||||
|
|
||||||
def constrain(self, other):
|
def constrain(self, other):
|
||||||
if not self.versions.overlaps(other.versions):
|
if not self.versions.overlaps(other.versions):
|
||||||
|
@ -720,21 +734,63 @@ def constrain(self, other):
|
||||||
self.variants.update(other.variants)
|
self.variants.update(other.variants)
|
||||||
self.architecture = self.architecture or other.architecture
|
self.architecture = self.architecture or other.architecture
|
||||||
|
|
||||||
|
# TODO: constrain dependencies, too.
|
||||||
|
|
||||||
def satisfies(self, other):
|
|
||||||
|
def satisfies(self, other, **kwargs):
|
||||||
if not isinstance(other, Spec):
|
if not isinstance(other, Spec):
|
||||||
other = Spec(other)
|
other = Spec(other)
|
||||||
|
|
||||||
def sat(attribute):
|
# First thing we care about is whether the name matches
|
||||||
|
if self.name != other.name:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# This function simplifies null checking below
|
||||||
|
def check(attribute, op):
|
||||||
s = getattr(self, attribute)
|
s = getattr(self, attribute)
|
||||||
o = getattr(other, attribute)
|
o = getattr(other, attribute)
|
||||||
return not s or not o or s.satisfies(o)
|
return not s or not o or op(s,o)
|
||||||
|
|
||||||
return (self.name == other.name and
|
# All these attrs have satisfies criteria of their own
|
||||||
all(sat(attr) for attr in
|
for attr in ('versions', 'variants', 'compiler'):
|
||||||
('versions', 'variants', 'compiler', 'architecture')) and
|
if not check(attr, lambda s, o: s.satisfies(o)):
|
||||||
# TODO: what does it mean to satisfy deps?
|
return False
|
||||||
self.dependencies.satisfies(other.dependencies))
|
|
||||||
|
# Architecture is just a string
|
||||||
|
# TODO: inviestigate making an Architecture class for symmetry
|
||||||
|
if not check('architecture', lambda s,o: s == o):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if kwargs.get('deps', True):
|
||||||
|
return self.satisfies_dependencies(other)
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def satisfies_dependencies(self, other):
|
||||||
|
"""This checks constraints on common dependencies against each other."""
|
||||||
|
# if either spec doesn't restrict dependencies then both are compatible.
|
||||||
|
if not self.dependencies or not other.dependencies:
|
||||||
|
return True
|
||||||
|
|
||||||
|
common = set(s.name for s in self.preorder_traversal(root=False))
|
||||||
|
common.intersection_update(s.name for s in other.preorder_traversal(root=False))
|
||||||
|
|
||||||
|
# Handle first-order constraints directly
|
||||||
|
for name in common:
|
||||||
|
if not self[name].satisfies(other[name]):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# For virtual dependencies, we need to dig a little deeper.
|
||||||
|
self_index = packages.ProviderIndex(self.preorder_traversal())
|
||||||
|
other_index = packages.ProviderIndex(other.preorder_traversal())
|
||||||
|
|
||||||
|
return self_index.satisfies(other_index)
|
||||||
|
|
||||||
|
|
||||||
|
def virtual_dependencies(self):
|
||||||
|
"""Return list of any virtual deps in this spec."""
|
||||||
|
return [spec for spec in self.preorder_traversal() if spec.virtual]
|
||||||
|
|
||||||
|
|
||||||
def _dup(self, other, **kwargs):
|
def _dup(self, other, **kwargs):
|
||||||
|
@ -848,7 +904,7 @@ def write(s, c):
|
||||||
if c == '_':
|
if c == '_':
|
||||||
out.write(self.name)
|
out.write(self.name)
|
||||||
elif c == '@':
|
elif c == '@':
|
||||||
if self.versions and self.versions != VersionList([':']):
|
if self.versions and self.versions != _any_version:
|
||||||
write(c + str(self.versions), c)
|
write(c + str(self.versions), c)
|
||||||
elif c == '%':
|
elif c == '%':
|
||||||
if self.compiler:
|
if self.compiler:
|
||||||
|
@ -1078,6 +1134,8 @@ def compiler(self):
|
||||||
vlist = self.version_list()
|
vlist = self.version_list()
|
||||||
for version in vlist:
|
for version in vlist:
|
||||||
compiler._add_version(version)
|
compiler._add_version(version)
|
||||||
|
else:
|
||||||
|
compiler.versions = VersionList(':')
|
||||||
return compiler
|
return compiler
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
'url_parse',
|
'url_parse',
|
||||||
'stage',
|
'stage',
|
||||||
'spec_syntax',
|
'spec_syntax',
|
||||||
|
'spec_semantics',
|
||||||
'spec_dag',
|
'spec_dag',
|
||||||
'concretize',
|
'concretize',
|
||||||
'multimethod']
|
'multimethod']
|
||||||
|
|
|
@ -37,3 +37,65 @@ def version_overlap(self):
|
||||||
def version_overlap(self):
|
def version_overlap(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Use these to test whether the default method is called when no
|
||||||
|
# match is found. This also tests whether we can switch methods
|
||||||
|
# on compilers
|
||||||
|
#
|
||||||
|
def has_a_default(self):
|
||||||
|
return 'default'
|
||||||
|
|
||||||
|
@when('%gcc')
|
||||||
|
def has_a_default(self):
|
||||||
|
return 'gcc'
|
||||||
|
|
||||||
|
@when('%intel')
|
||||||
|
def has_a_default(self):
|
||||||
|
return 'intel'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Make sure we can switch methods on different architectures
|
||||||
|
#
|
||||||
|
@when('=x86_64')
|
||||||
|
def different_by_architecture(self):
|
||||||
|
return 'x86_64'
|
||||||
|
|
||||||
|
@when('=ppc64')
|
||||||
|
def different_by_architecture(self):
|
||||||
|
return 'ppc64'
|
||||||
|
|
||||||
|
@when('=ppc32')
|
||||||
|
def different_by_architecture(self):
|
||||||
|
return 'ppc32'
|
||||||
|
|
||||||
|
@when('=arm64')
|
||||||
|
def different_by_architecture(self):
|
||||||
|
return 'arm64'
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Make sure we can switch methods on different dependencies
|
||||||
|
#
|
||||||
|
@when('^mpich')
|
||||||
|
def different_by_dep(self):
|
||||||
|
return 'mpich'
|
||||||
|
|
||||||
|
@when('^zmpi')
|
||||||
|
def different_by_dep(self):
|
||||||
|
return 'zmpi'
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Make sure we can switch on virtual dependencies
|
||||||
|
#
|
||||||
|
@when('^mpi@2:')
|
||||||
|
def different_by_virtual_dep(self):
|
||||||
|
return 'mpi@2:'
|
||||||
|
|
||||||
|
@when('^mpi@:1')
|
||||||
|
def different_by_virtual_dep(self):
|
||||||
|
return 'mpi@:1'
|
||||||
|
|
|
@ -8,7 +8,7 @@ class Zmpi(Package):
|
||||||
|
|
||||||
versions = { '1.0' : 'foobarbaz' }
|
versions = { '1.0' : 'foobarbaz' }
|
||||||
|
|
||||||
provides('mpi@10.0:')
|
provides('mpi@:10.0')
|
||||||
depends_on('fake')
|
depends_on('fake')
|
||||||
|
|
||||||
def install(self, prefix):
|
def install(self, prefix):
|
||||||
|
|
|
@ -15,7 +15,8 @@ class MultiMethodTest(MockPackagesTest):
|
||||||
|
|
||||||
def test_no_version_match(self):
|
def test_no_version_match(self):
|
||||||
pkg = packages.get('multimethod@2.0')
|
pkg = packages.get('multimethod@2.0')
|
||||||
self.assertRaises(NoSuchMethodVersionError, pkg.no_version_2)
|
self.assertRaises(NoSuchMethodError, pkg.no_version_2)
|
||||||
|
|
||||||
|
|
||||||
def test_one_version_match(self):
|
def test_one_version_match(self):
|
||||||
pkg = packages.get('multimethod@1.0')
|
pkg = packages.get('multimethod@1.0')
|
||||||
|
@ -28,7 +29,56 @@ def test_one_version_match(self):
|
||||||
self.assertEqual(pkg.no_version_2(), 4)
|
self.assertEqual(pkg.no_version_2(), 4)
|
||||||
|
|
||||||
|
|
||||||
def test_multiple_matches(self):
|
def test_version_overlap(self):
|
||||||
pkg = packages.get('multimethod@3.0')
|
pkg = packages.get('multimethod@3.0')
|
||||||
self.assertRaises(AmbiguousMethodVersionError, pkg.version_overlap)
|
self.assertRaises(AmbiguousMethodError, pkg.version_overlap)
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_works(self):
|
||||||
|
pkg = packages.get('multimethod%gcc')
|
||||||
|
self.assertEqual(pkg.has_a_default(), 'gcc')
|
||||||
|
|
||||||
|
pkg = packages.get('multimethod%intel')
|
||||||
|
self.assertEqual(pkg.has_a_default(), 'intel')
|
||||||
|
|
||||||
|
pkg = packages.get('multimethod%pgi')
|
||||||
|
self.assertEqual(pkg.has_a_default(), 'default')
|
||||||
|
|
||||||
|
|
||||||
|
def test_architecture_match(self):
|
||||||
|
pkg = packages.get('multimethod=x86_64')
|
||||||
|
self.assertEqual(pkg.different_by_architecture(), 'x86_64')
|
||||||
|
|
||||||
|
pkg = packages.get('multimethod=ppc64')
|
||||||
|
self.assertEqual(pkg.different_by_architecture(), 'ppc64')
|
||||||
|
|
||||||
|
pkg = packages.get('multimethod=ppc32')
|
||||||
|
self.assertEqual(pkg.different_by_architecture(), 'ppc32')
|
||||||
|
|
||||||
|
pkg = packages.get('multimethod=arm64')
|
||||||
|
self.assertEqual(pkg.different_by_architecture(), 'arm64')
|
||||||
|
|
||||||
|
pkg = packages.get('multimethod=macos')
|
||||||
|
self.assertRaises(NoSuchMethodError, pkg.different_by_architecture)
|
||||||
|
|
||||||
|
|
||||||
|
def test_dependency_match(self):
|
||||||
|
pkg = packages.get('multimethod^zmpi')
|
||||||
|
self.assertEqual(pkg.different_by_dep(), 'zmpi')
|
||||||
|
|
||||||
|
pkg = packages.get('multimethod^mpich')
|
||||||
|
self.assertEqual(pkg.different_by_dep(), 'mpich')
|
||||||
|
|
||||||
|
|
||||||
|
def test_ambiguous_dep(self):
|
||||||
|
"""If we try to switch on some entirely different dep, it's ambiguous"""
|
||||||
|
pkg = packages.get('multimethod^foobar')
|
||||||
|
self.assertRaises(AmbiguousMethodError, pkg.different_by_dep)
|
||||||
|
|
||||||
|
|
||||||
|
def test_one_dep_match(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_one_dep_match(self):
|
||||||
|
pass
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
"""
|
"""
|
||||||
These tests check validation of dummy packages. You can find the dummy
|
These tests check Spec DAG operations using dummy packages.
|
||||||
packages directories that these tests use in:
|
You can find the dummy packages here::
|
||||||
|
|
||||||
spack/lib/spack/spack/test/mock_packages
|
spack/lib/spack/spack/test/mock_packages
|
||||||
|
|
||||||
Each test validates conditions with the packages in those directories.
|
|
||||||
"""
|
"""
|
||||||
import spack
|
import spack
|
||||||
import spack.package
|
import spack.package
|
||||||
|
@ -15,7 +13,7 @@
|
||||||
from spack.test.mock_packages_test import *
|
from spack.test.mock_packages_test import *
|
||||||
|
|
||||||
|
|
||||||
class ValidationTest(MockPackagesTest):
|
class SpecDagTest(MockPackagesTest):
|
||||||
|
|
||||||
def test_conflicting_package_constraints(self):
|
def test_conflicting_package_constraints(self):
|
||||||
set_pkg_dep('mpileaks', 'mpich@1.0')
|
set_pkg_dep('mpileaks', 'mpich@1.0')
|
||||||
|
@ -279,6 +277,8 @@ def test_normalize_with_virtual_package(self):
|
||||||
|
|
||||||
def test_contains(self):
|
def test_contains(self):
|
||||||
spec = Spec('mpileaks ^mpi ^libelf@1.8.11 ^libdwarf')
|
spec = Spec('mpileaks ^mpi ^libelf@1.8.11 ^libdwarf')
|
||||||
|
|
||||||
|
print [s for s in spec.preorder_traversal()]
|
||||||
self.assertIn(Spec('mpi'), spec)
|
self.assertIn(Spec('mpi'), spec)
|
||||||
self.assertIn(Spec('libelf'), spec)
|
self.assertIn(Spec('libelf'), spec)
|
||||||
self.assertIn(Spec('libelf@1.8.11'), spec)
|
self.assertIn(Spec('libelf@1.8.11'), spec)
|
||||||
|
|
141
lib/spack/spack/test/spec_semantics.py
Normal file
141
lib/spack/spack/test/spec_semantics.py
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
import unittest
|
||||||
|
from spack.spec import *
|
||||||
|
from spack.test.mock_packages_test import *
|
||||||
|
|
||||||
|
class SpecSematicsTest(MockPackagesTest):
|
||||||
|
"""This tests satisfies(), constrain() and other semantic operations
|
||||||
|
on specs."""
|
||||||
|
|
||||||
|
# ================================================================================
|
||||||
|
# Utility functions to set everything up.
|
||||||
|
# ================================================================================
|
||||||
|
def check_satisfies(self, lspec, rspec):
|
||||||
|
l, r = Spec(lspec), Spec(rspec)
|
||||||
|
self.assertTrue(l.satisfies(r))
|
||||||
|
self.assertTrue(r.satisfies(l))
|
||||||
|
|
||||||
|
try:
|
||||||
|
l.constrain(r)
|
||||||
|
r.constrain(l)
|
||||||
|
except SpecError, e:
|
||||||
|
self.fail("Got a SpecError in constrain!", e.message)
|
||||||
|
|
||||||
|
|
||||||
|
def check_unsatisfiable(self, lspec, rspec):
|
||||||
|
l, r = Spec(lspec), Spec(rspec)
|
||||||
|
self.assertFalse(l.satisfies(r))
|
||||||
|
self.assertFalse(r.satisfies(l))
|
||||||
|
|
||||||
|
self.assertRaises(UnsatisfiableSpecError, l.constrain, r)
|
||||||
|
self.assertRaises(UnsatisfiableSpecError, r.constrain, l)
|
||||||
|
|
||||||
|
|
||||||
|
def check_constrain(self, expected, constrained, constraint):
|
||||||
|
exp = Spec(expected)
|
||||||
|
constrained = Spec(constrained)
|
||||||
|
constraint = Spec(constraint)
|
||||||
|
constrained.constrain(constraint)
|
||||||
|
self.assertEqual(exp, constrained)
|
||||||
|
|
||||||
|
|
||||||
|
def check_invalid_constraint(self, constrained, constraint):
|
||||||
|
constrained = Spec(constrained)
|
||||||
|
constraint = Spec(constraint)
|
||||||
|
self.assertRaises(UnsatisfiableSpecError, constrained.constrain, constraint)
|
||||||
|
|
||||||
|
|
||||||
|
# ================================================================================
|
||||||
|
# Satisfiability and constraints
|
||||||
|
# ================================================================================
|
||||||
|
def test_satisfies(self):
|
||||||
|
self.check_satisfies('libelf@0.8.13', 'libelf@0:1')
|
||||||
|
self.check_satisfies('libdwarf^libelf@0.8.13', 'libdwarf^libelf@0:1')
|
||||||
|
|
||||||
|
|
||||||
|
def test_satisfies_compiler(self):
|
||||||
|
self.check_satisfies('foo%gcc', 'foo%gcc')
|
||||||
|
self.check_satisfies('foo%intel', 'foo%intel')
|
||||||
|
self.check_unsatisfiable('foo%intel', 'foo%gcc')
|
||||||
|
self.check_unsatisfiable('foo%intel', 'foo%pgi')
|
||||||
|
|
||||||
|
|
||||||
|
def test_satisfies_compiler_version(self):
|
||||||
|
self.check_satisfies('foo%gcc', 'foo%gcc@4.7.2')
|
||||||
|
self.check_satisfies('foo%intel', 'foo%intel@4.7.2')
|
||||||
|
|
||||||
|
self.check_satisfies('foo%pgi@4.5', 'foo%pgi@4.4:4.6')
|
||||||
|
self.check_satisfies('foo@2.0%pgi@4.5', 'foo@1:3%pgi@4.4:4.6')
|
||||||
|
|
||||||
|
self.check_unsatisfiable('foo%pgi@4.3', 'foo%pgi@4.4:4.6')
|
||||||
|
self.check_unsatisfiable('foo@4.0%pgi', 'foo@1:3%pgi')
|
||||||
|
self.check_unsatisfiable('foo@4.0%pgi@4.5', 'foo@1:3%pgi@4.4:4.6')
|
||||||
|
|
||||||
|
|
||||||
|
def test_satisfies_architecture(self):
|
||||||
|
self.check_satisfies('foo=chaos_5_x86_64_ib', 'foo=chaos_5_x86_64_ib')
|
||||||
|
self.check_satisfies('foo=bgqos_0', 'foo=bgqos_0')
|
||||||
|
|
||||||
|
self.check_unsatisfiable('foo=bgqos_0', 'foo=chaos_5_x86_64_ib')
|
||||||
|
self.check_unsatisfiable('foo=chaos_5_x86_64_ib', 'foo=bgqos_0')
|
||||||
|
|
||||||
|
|
||||||
|
def test_satisfies_dependencies(self):
|
||||||
|
# self.check_satisfies('mpileaks^mpich', 'mpileaks^mpich')
|
||||||
|
# self.check_satisfies('mpileaks^zmpi', 'mpileaks^zmpi')
|
||||||
|
self.check_unsatisfiable('mpileaks^mpich', 'mpileaks^zmpi')
|
||||||
|
self.check_unsatisfiable('mpileaks^zmpi', 'mpileaks^mpich')
|
||||||
|
|
||||||
|
|
||||||
|
def ztest_satisfies_dependency_versions(self):
|
||||||
|
self.check_satisfies('mpileaks^mpich@2.0', 'mpileaks^mpich@1:3')
|
||||||
|
self.check_unsatisfiable('mpileaks^mpich@1.2', 'mpileaks^mpich@2.0')
|
||||||
|
|
||||||
|
self.check_satisfies('mpileaks^mpich@2.0^callpath@1.5', 'mpileaks^mpich@1:3^callpath@1.4:1.6')
|
||||||
|
self.check_unsatisfiable('mpileaks^mpich@4.0^callpath@1.5', 'mpileaks^mpich@1:3^callpath@1.4:1.6')
|
||||||
|
self.check_unsatisfiable('mpileaks^mpich@2.0^callpath@1.7', 'mpileaks^mpich@1:3^callpath@1.4:1.6')
|
||||||
|
self.check_unsatisfiable('mpileaks^mpich@4.0^callpath@1.7', 'mpileaks^mpich@1:3^callpath@1.4:1.6')
|
||||||
|
|
||||||
|
|
||||||
|
def ztest_satisfies_virtual_dependencies(self):
|
||||||
|
self.check_satisfies('mpileaks^mpi', 'mpileaks^mpi')
|
||||||
|
self.check_satisfies('mpileaks^mpi', 'mpileaks^mpich')
|
||||||
|
|
||||||
|
self.check_satisfies('mpileaks^mpi', 'mpileaks^zmpi')
|
||||||
|
self.check_unsatisfiable('mpileaks^mpich', 'mpileaks^zmpi')
|
||||||
|
|
||||||
|
|
||||||
|
def ztest_satisfies_virtual_dependency_versions(self):
|
||||||
|
self.check_satisfies('mpileaks^mpi@1.5', 'mpileaks^mpi@1.2:1.6')
|
||||||
|
self.check_unsatisfiable('mpileaks^mpi@3', 'mpileaks^mpi@1.2:1.6')
|
||||||
|
|
||||||
|
self.check_satisfies('mpileaks^mpi@2:', 'mpileaks^mpich')
|
||||||
|
self.check_satisfies('mpileaks^mpi@2:', 'mpileaks^mpich@3.0.4')
|
||||||
|
self.check_satisfies('mpileaks^mpi@2:', 'mpileaks^mpich2@1.4')
|
||||||
|
|
||||||
|
self.check_unsatisfiable('mpileaks^mpi@3:', 'mpileaks^mpich2@1.4')
|
||||||
|
self.check_unsatisfiable('mpileaks^mpi@3:', 'mpileaks^mpich@1.0')
|
||||||
|
|
||||||
|
|
||||||
|
def test_constrain(self):
|
||||||
|
self.check_constrain('libelf@2.1:2.5', 'libelf@0:2.5', 'libelf@2.1:3')
|
||||||
|
self.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')
|
||||||
|
|
||||||
|
self.check_constrain('libelf+debug+foo', 'libelf+debug', 'libelf+foo')
|
||||||
|
self.check_constrain('libelf+debug+foo', 'libelf+debug', 'libelf+debug+foo')
|
||||||
|
|
||||||
|
self.check_constrain('libelf+debug~foo', 'libelf+debug', 'libelf~foo')
|
||||||
|
self.check_constrain('libelf+debug~foo', 'libelf+debug', 'libelf+debug~foo')
|
||||||
|
|
||||||
|
self.check_constrain('libelf=bgqos_0', 'libelf=bgqos_0', 'libelf=bgqos_0')
|
||||||
|
self.check_constrain('libelf=bgqos_0', 'libelf', 'libelf=bgqos_0')
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_constraint(self):
|
||||||
|
self.check_invalid_constraint('libelf@0:2.0', 'libelf@2.1:3')
|
||||||
|
self.check_invalid_constraint('libelf@0:2.5%gcc@4.8:4.9', 'libelf@2.1:3%gcc@4.5:4.7')
|
||||||
|
|
||||||
|
self.check_invalid_constraint('libelf+debug', 'libelf~debug')
|
||||||
|
self.check_invalid_constraint('libelf+debug~foo', 'libelf+debug+foo')
|
||||||
|
|
||||||
|
self.check_invalid_constraint('libelf=bgqos_0', 'libelf=x86_54')
|
|
@ -29,7 +29,7 @@
|
||||||
Token(ID, '8.1_1e')]
|
Token(ID, '8.1_1e')]
|
||||||
|
|
||||||
|
|
||||||
class SpecTest(unittest.TestCase):
|
class SpecSyntaxTest(unittest.TestCase):
|
||||||
# ================================================================================
|
# ================================================================================
|
||||||
# Parse checks
|
# Parse checks
|
||||||
# ================================================================================
|
# ================================================================================
|
||||||
|
@ -59,42 +59,6 @@ def check_lex(self, tokens, spec):
|
||||||
# Only check the type for non-identifiers.
|
# Only check the type for non-identifiers.
|
||||||
self.assertEqual(tok.type, spec_tok.type)
|
self.assertEqual(tok.type, spec_tok.type)
|
||||||
|
|
||||||
|
|
||||||
def check_satisfies(self, lspec, rspec):
|
|
||||||
l, r = Spec(lspec), Spec(rspec)
|
|
||||||
self.assertTrue(l.satisfies(r))
|
|
||||||
self.assertTrue(r.satisfies(l))
|
|
||||||
|
|
||||||
try:
|
|
||||||
l.constrain(r)
|
|
||||||
r.constrain(l)
|
|
||||||
except SpecError, e:
|
|
||||||
self.fail("Got a SpecError in constrain!", e.message)
|
|
||||||
|
|
||||||
|
|
||||||
def assert_unsatisfiable(lspec, rspec):
|
|
||||||
l, r = Spec(lspec), Spec(rspec)
|
|
||||||
self.assertFalse(l.satisfies(r))
|
|
||||||
self.assertFalse(r.satisfies(l))
|
|
||||||
|
|
||||||
self.assertRaises(l.constrain, r)
|
|
||||||
self.assertRaises(r.constrain, l)
|
|
||||||
|
|
||||||
|
|
||||||
def check_constrain(self, expected, constrained, constraint):
|
|
||||||
exp = Spec(expected)
|
|
||||||
constrained = Spec(constrained)
|
|
||||||
constraint = Spec(constraint)
|
|
||||||
constrained.constrain(constraint)
|
|
||||||
self.assertEqual(exp, constrained)
|
|
||||||
|
|
||||||
|
|
||||||
def check_invalid_constraint(self, constrained, constraint):
|
|
||||||
constrained = Spec(constrained)
|
|
||||||
constraint = Spec(constraint)
|
|
||||||
self.assertRaises(UnsatisfiableSpecError, constrained.constrain, constraint)
|
|
||||||
|
|
||||||
|
|
||||||
# ================================================================================
|
# ================================================================================
|
||||||
# Parse checks
|
# Parse checks
|
||||||
# ===============================================================================
|
# ===============================================================================
|
||||||
|
@ -153,39 +117,6 @@ def test_duplicate_compiler(self):
|
||||||
self.assertRaises(DuplicateCompilerError, self.check_parse, "x ^y%gcc%intel")
|
self.assertRaises(DuplicateCompilerError, self.check_parse, "x ^y%gcc%intel")
|
||||||
|
|
||||||
|
|
||||||
# ================================================================================
|
|
||||||
# Satisfiability and constraints
|
|
||||||
# ================================================================================
|
|
||||||
def test_satisfies(self):
|
|
||||||
self.check_satisfies('libelf@0.8.13', 'libelf@0:1')
|
|
||||||
self.check_satisfies('libdwarf^libelf@0.8.13', 'libdwarf^libelf@0:1')
|
|
||||||
|
|
||||||
|
|
||||||
def test_constrain(self):
|
|
||||||
self.check_constrain('libelf@2.1:2.5', 'libelf@0:2.5', 'libelf@2.1:3')
|
|
||||||
self.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')
|
|
||||||
|
|
||||||
self.check_constrain('libelf+debug+foo', 'libelf+debug', 'libelf+foo')
|
|
||||||
self.check_constrain('libelf+debug+foo', 'libelf+debug', 'libelf+debug+foo')
|
|
||||||
|
|
||||||
self.check_constrain('libelf+debug~foo', 'libelf+debug', 'libelf~foo')
|
|
||||||
self.check_constrain('libelf+debug~foo', 'libelf+debug', 'libelf+debug~foo')
|
|
||||||
|
|
||||||
self.check_constrain('libelf=bgqos_0', 'libelf=bgqos_0', 'libelf=bgqos_0')
|
|
||||||
self.check_constrain('libelf=bgqos_0', 'libelf', 'libelf=bgqos_0')
|
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_constraint(self):
|
|
||||||
self.check_invalid_constraint('libelf@0:2.0', 'libelf@2.1:3')
|
|
||||||
self.check_invalid_constraint('libelf@0:2.5%gcc@4.8:4.9', 'libelf@2.1:3%gcc@4.5:4.7')
|
|
||||||
|
|
||||||
self.check_invalid_constraint('libelf+debug', 'libelf~debug')
|
|
||||||
self.check_invalid_constraint('libelf+debug~foo', 'libelf+debug+foo')
|
|
||||||
|
|
||||||
self.check_invalid_constraint('libelf=bgqos_0', 'libelf=x86_54')
|
|
||||||
|
|
||||||
|
|
||||||
# ================================================================================
|
# ================================================================================
|
||||||
# Lex checks
|
# Lex checks
|
||||||
# ================================================================================
|
# ================================================================================
|
||||||
|
|
Loading…
Reference in a new issue