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:
Todd Gamblin 2013-12-22 17:55:58 -08:00
parent f7706d231d
commit 7088cdf25f
11 changed files with 419 additions and 121 deletions

View file

@ -11,6 +11,10 @@ def supported_compilers():
return [c for c in list_modules(spack.compilers_path)]
def supported(compiler):
return compiler in supported_compilers()
@memoized
def default_compiler():
from spack.spec import Compiler

View file

@ -26,7 +26,7 @@
import spack.architecture
import spack.error
from spack.util.lang import *
from spack.spec import parse_local_spec
from spack.spec import parse_local_spec, Spec
class SpecMultiMethod(object):
@ -94,19 +94,37 @@ def __call__(self, package_self, *args, **kwargs):
spec = package_self.spec
matching_specs = [s for s in self.method_map if s.satisfies(spec)]
if not matching_specs and self.default is None:
raise NoSuchMethodVersionError(type(package_self), self.__name__,
spec, self.method_map.keys())
elif len(matching_specs) > 1:
raise AmbiguousMethodVersionError(type(package_self), self.__name__,
# from pprint import pprint
# print "========"
# print "called with " + str(spec)
# 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)
method = self.method_map[matching_specs[0]]
return method(package_self, *args, **kwargs)
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):
@ -193,19 +211,19 @@ def __init__(self, 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."""
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"
% (cls.__name__, method_name, spec,
", ".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."""
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"
% (cls.__name__, method_name, spec,
",".join(str(s) for s in matching_specs)))

View file

@ -45,7 +45,7 @@ class ProviderIndex(object):
{ mpi@:1.1 : mpich,
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.
"""
def __init__(self, specs, **kwargs):
@ -61,7 +61,7 @@ def __init__(self, specs, **kwargs):
pkg = spec.package
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
if provided_name not in self.providers:
self.providers[provided_name] = {}
@ -79,8 +79,8 @@ def __init__(self, specs, **kwargs):
def providers_for(self, *vpkg_specs):
"""Gives names of all packages that provide virtual packages
with the supplied names."""
"""Gives specs of all packages that provide virtual packages
with the supplied specs."""
providers = set()
for vspec in vpkg_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.
if vspec.name in self.providers:
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)
# Return providers in order
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
def get(spec):
if spec.virtual:

View file

@ -67,15 +67,16 @@
expansion when it is the first character in an id typed on the command line.
"""
import sys
import itertools
import hashlib
from StringIO import StringIO
import tty
import hashlib
import spack.parse
import spack.error
import spack.compilers
import spack.compilers.gcc
import spack.packages as packages
import spack.tty as tty
from spack.version import *
from spack.color import *
@ -102,7 +103,11 @@
'^' : dependency_color }
"""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):
@ -134,7 +139,7 @@ def __call__(self, match):
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
@ -143,9 +148,6 @@ class Compiler(object):
versions that a package should be built with. Compilers have a
name and a version list. """
def __init__(self, name, version=None):
if name not in spack.compilers.supported_compilers():
raise UnknownCompilerError(name)
self.name = name
self.versions = VersionList()
if version:
@ -193,7 +195,7 @@ def _cmp_key(self):
def __str__(self):
out = self.name
if self.versions:
if self.versions and self.versions != _any_version:
vlist = ",".join(str(v) for v in self.versions)
out += "@%s" % vlist
return out
@ -242,11 +244,6 @@ def concrete(self):
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):
sha = hashlib.sha1()
sha.update(str(self))
@ -656,8 +653,8 @@ def normalize(self):
TODO: normalize should probably implement some form of cycle detection,
to ensure that the spec is actually a DAG.
"""
# Ensure first that all packages in the DAG exist.
self.validate_package_names()
# Ensure first that all packages & compilers in the DAG exist.
self.validate_names()
# Then ensure that the packages referenced are sane, that 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))
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():
# Don't get a package for a virtual name.
if not spec.virtual:
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):
if not self.versions.overlaps(other.versions):
@ -720,21 +734,63 @@ def constrain(self, other):
self.variants.update(other.variants)
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):
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)
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(sat(attr) for attr in
('versions', 'variants', 'compiler', 'architecture')) and
# TODO: what does it mean to satisfy deps?
self.dependencies.satisfies(other.dependencies))
# All these attrs have satisfies criteria of their own
for attr in ('versions', 'variants', 'compiler'):
if not check(attr, lambda s, o: s.satisfies(o)):
return False
# 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):
@ -848,7 +904,7 @@ def write(s, c):
if c == '_':
out.write(self.name)
elif c == '@':
if self.versions and self.versions != VersionList([':']):
if self.versions and self.versions != _any_version:
write(c + str(self.versions), c)
elif c == '%':
if self.compiler:
@ -1078,6 +1134,8 @@ def compiler(self):
vlist = self.version_list()
for version in vlist:
compiler._add_version(version)
else:
compiler.versions = VersionList(':')
return compiler

View file

@ -10,6 +10,7 @@
'url_parse',
'stage',
'spec_syntax',
'spec_semantics',
'spec_dag',
'concretize',
'multimethod']

View file

@ -37,3 +37,65 @@ def version_overlap(self):
def version_overlap(self):
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'

View file

@ -8,7 +8,7 @@ class Zmpi(Package):
versions = { '1.0' : 'foobarbaz' }
provides('mpi@10.0:')
provides('mpi@:10.0')
depends_on('fake')
def install(self, prefix):

View file

@ -15,7 +15,8 @@ class MultiMethodTest(MockPackagesTest):
def test_no_version_match(self):
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):
pkg = packages.get('multimethod@1.0')
@ -28,7 +29,56 @@ def test_one_version_match(self):
self.assertEqual(pkg.no_version_2(), 4)
def test_multiple_matches(self):
def test_version_overlap(self):
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

View file

@ -1,10 +1,8 @@
"""
These tests check validation of dummy packages. You can find the dummy
packages directories that these tests use in:
These tests check Spec DAG operations using dummy packages.
You can find the dummy packages here::
spack/lib/spack/spack/test/mock_packages
Each test validates conditions with the packages in those directories.
"""
import spack
import spack.package
@ -15,7 +13,7 @@
from spack.test.mock_packages_test import *
class ValidationTest(MockPackagesTest):
class SpecDagTest(MockPackagesTest):
def test_conflicting_package_constraints(self):
set_pkg_dep('mpileaks', 'mpich@1.0')
@ -279,6 +277,8 @@ def test_normalize_with_virtual_package(self):
def test_contains(self):
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('libelf'), spec)
self.assertIn(Spec('libelf@1.8.11'), spec)

View 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')

View file

@ -29,7 +29,7 @@
Token(ID, '8.1_1e')]
class SpecTest(unittest.TestCase):
class SpecSyntaxTest(unittest.TestCase):
# ================================================================================
# Parse checks
# ================================================================================
@ -59,42 +59,6 @@ def check_lex(self, tokens, spec):
# Only check the type for non-identifiers.
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
# ===============================================================================
@ -153,39 +117,6 @@ def test_duplicate_compiler(self):
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
# ================================================================================