Added unit tests for spec normalization.
This commit is contained in:
		@@ -380,7 +380,8 @@ def preorder_traversal(self, visited=None):
 | 
			
		||||
        visited.add(self.name)
 | 
			
		||||
 | 
			
		||||
        yield self
 | 
			
		||||
        for name, spec in self.dependencies.iteritems():
 | 
			
		||||
        for name in sorted(self.dependencies.keys()):
 | 
			
		||||
            spec = self.dependencies[name]
 | 
			
		||||
            for pkg in packages.get(name).preorder_traversal(visited):
 | 
			
		||||
                yield pkg
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -78,6 +78,7 @@
 | 
			
		||||
from spack.util.lang import *
 | 
			
		||||
from spack.util.string import *
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
"""This map determines the coloring of specs when using color output.
 | 
			
		||||
   We make the fields different colors to enhance readability.
 | 
			
		||||
   See spack.color for descriptions of the color codes.
 | 
			
		||||
@@ -226,7 +227,7 @@ def __str__(self):
 | 
			
		||||
 | 
			
		||||
@key_ordering
 | 
			
		||||
class Spec(object):
 | 
			
		||||
    def __init__(self, spec_like):
 | 
			
		||||
    def __init__(self, spec_like, *dep_like):
 | 
			
		||||
        # Copy if spec_like is a Spec.
 | 
			
		||||
        if type(spec_like) == Spec:
 | 
			
		||||
            self._dup(spec_like)
 | 
			
		||||
@@ -254,6 +255,16 @@ def __init__(self, spec_like):
 | 
			
		||||
        self.compiler = other.compiler
 | 
			
		||||
        self.dependencies = other.dependencies
 | 
			
		||||
 | 
			
		||||
        # This allows users to construct a spec DAG with literals.
 | 
			
		||||
        # Note that given two specs a and b, Spec(a) copies a, but
 | 
			
		||||
        # Spec(a, b) will copy a but just add b as a dep.
 | 
			
		||||
        for dep in dep_like:
 | 
			
		||||
            if type(dep) == str:
 | 
			
		||||
                dep_spec = Spec(dep)
 | 
			
		||||
                self.dependencies[dep_spec.name] = dep_spec
 | 
			
		||||
            elif type(dep) == Spec:
 | 
			
		||||
                self.dependencies[dep.name] = dep
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    #
 | 
			
		||||
    # Private routines here are called by the parser when building a spec.
 | 
			
		||||
@@ -315,17 +326,27 @@ def concrete(self):
 | 
			
		||||
                    and self.dependencies.concrete)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def preorder_traversal(self, visited=None):
 | 
			
		||||
    def preorder_traversal(self, visited=None, d=0, **kwargs):
 | 
			
		||||
        unique = kwargs.setdefault('unique', True)
 | 
			
		||||
        depth  = kwargs.setdefault('depth', False)
 | 
			
		||||
        keyfun = kwargs.setdefault('key', id)
 | 
			
		||||
        noroot = kwargs.setdefault('noroot', False)
 | 
			
		||||
 | 
			
		||||
        if visited is None:
 | 
			
		||||
            visited = set()
 | 
			
		||||
 | 
			
		||||
        if id(self) in visited:
 | 
			
		||||
        if keyfun(self) in visited:
 | 
			
		||||
            if not unique:
 | 
			
		||||
                yield (d, self) if depth else self
 | 
			
		||||
            return
 | 
			
		||||
        visited.add(id(self))
 | 
			
		||||
        visited.add(keyfun(self))
 | 
			
		||||
 | 
			
		||||
        yield self
 | 
			
		||||
        for dep in self.dependencies.itervalues():
 | 
			
		||||
            for spec in dep.preorder_traversal(visited):
 | 
			
		||||
        if d > 0 or not noroot:
 | 
			
		||||
            yield (d, self) if depth else self
 | 
			
		||||
 | 
			
		||||
        for key in sorted(self.dependencies.keys()):
 | 
			
		||||
            for spec in self.dependencies[key].preorder_traversal(
 | 
			
		||||
                    visited, d+1, **kwargs):
 | 
			
		||||
                yield spec
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -368,10 +389,14 @@ def _concretize(self):
 | 
			
		||||
        # Ensure dependencies have right versions
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def flatten(self):
 | 
			
		||||
        """Pull all dependencies up to the root (this spec).
 | 
			
		||||
           Merge constraints for dependencies with the same name, and if they
 | 
			
		||||
           conflict, throw an exception. """
 | 
			
		||||
    def flat_dependencies(self):
 | 
			
		||||
        """Return a DependencyMap containing all dependencies with their
 | 
			
		||||
           constraints merged.  If there are any conflicts, throw an exception.
 | 
			
		||||
 | 
			
		||||
           This will work even on specs that are not normalized; i.e. specs
 | 
			
		||||
           that have two instances of the same dependency in the DAG.
 | 
			
		||||
           This is used as the first step of normalization.
 | 
			
		||||
        """
 | 
			
		||||
        # This ensures that the package descriptions themselves are consistent
 | 
			
		||||
        self.package.validate_dependencies()
 | 
			
		||||
 | 
			
		||||
@@ -393,7 +418,14 @@ def flatten(self):
 | 
			
		||||
            # so this means OUR code is not sane!
 | 
			
		||||
            raise InconsistentSpecError("Invalid Spec DAG: %s" % e.message)
 | 
			
		||||
 | 
			
		||||
        self.dependencies = flat_deps
 | 
			
		||||
        return flat_deps
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def flatten(self):
 | 
			
		||||
        """Pull all dependencies up to the root (this spec).
 | 
			
		||||
           Merge constraints for dependencies with the same name, and if they
 | 
			
		||||
           conflict, throw an exception. """
 | 
			
		||||
        self.dependencies = self.flat_dependencies()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def _normalize_helper(self, visited, spec_deps):
 | 
			
		||||
@@ -432,11 +464,8 @@ def normalize(self):
 | 
			
		||||
 | 
			
		||||
        # Then ensure that the packages mentioned are sane, that the
 | 
			
		||||
        # provided spec is sane, and that all dependency specs are in the
 | 
			
		||||
        # root node of the spec.  Flatten will do this for us.
 | 
			
		||||
        self.flatten()
 | 
			
		||||
 | 
			
		||||
        # Now that we're flat we can get all our dependencies at once.
 | 
			
		||||
        spec_deps = self.dependencies
 | 
			
		||||
        # root node of the spec.  flat_dependencies will do this for us.
 | 
			
		||||
        spec_deps = self.flat_dependencies()
 | 
			
		||||
        self.dependencies = DependencyMap()
 | 
			
		||||
 | 
			
		||||
        visited = set()
 | 
			
		||||
@@ -535,7 +564,7 @@ def colorized(self):
 | 
			
		||||
        return colorize_spec(self)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def str_without_deps(self):
 | 
			
		||||
    def str_no_deps(self):
 | 
			
		||||
        out = self.name
 | 
			
		||||
 | 
			
		||||
        # If the version range is entirely open, omit it
 | 
			
		||||
@@ -553,12 +582,19 @@ def str_without_deps(self):
 | 
			
		||||
        return out
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def tree(self, indent=""):
 | 
			
		||||
    def tree(self):
 | 
			
		||||
        """Prints out this spec and its dependencies, tree-formatted
 | 
			
		||||
           with indentation.  Each node also has an id."""
 | 
			
		||||
        out = indent + self.str_without_deps()
 | 
			
		||||
        for dep in sorted(self.dependencies.keys()):
 | 
			
		||||
            out += "\n" + self.dependencies[dep].tree(indent + "    ")
 | 
			
		||||
           with indentation."""
 | 
			
		||||
        out = ""
 | 
			
		||||
        cur_id = 0
 | 
			
		||||
        ids = {}
 | 
			
		||||
        for d, node in self.preorder_traversal(unique=False, depth=True):
 | 
			
		||||
            if not id(node) in ids:
 | 
			
		||||
                cur_id += 1
 | 
			
		||||
                ids[id(node)] = cur_id
 | 
			
		||||
            out += str(ids[id(node)])
 | 
			
		||||
            out += " "+ ("    " * d)
 | 
			
		||||
            out += node.str_no_deps() + "\n"
 | 
			
		||||
        return out
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -567,7 +603,11 @@ def __repr__(self):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return self.str_without_deps() + str(self.dependencies)
 | 
			
		||||
        byname = lambda d: d.name
 | 
			
		||||
        deps = self.preorder_traversal(key=byname, noroot=True)
 | 
			
		||||
        sorted_deps = sorted(deps, key=byname)
 | 
			
		||||
        dep_string = ''.join("^" + dep.str_no_deps() for dep in sorted_deps)
 | 
			
		||||
        return self.str_no_deps() + dep_string
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
 
 | 
			
		||||
@@ -115,3 +115,72 @@ def test_unsatisfiable_architecture(self):
 | 
			
		||||
        set_pkg_dep('mpileaks', 'mpich=bgqos_0')
 | 
			
		||||
        spec = Spec('mpileaks ^mpich=sles_10_ppc64 ^callpath ^dyninst ^libelf ^libdwarf')
 | 
			
		||||
        self.assertRaises(spack.spec.UnsatisfiableArchitectureSpecError, spec.normalize)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def test_invalid_dep(self):
 | 
			
		||||
        spec = Spec('libelf ^mpich')
 | 
			
		||||
        self.assertRaises(spack.spec.InvalidDependencyException, spec.normalize)
 | 
			
		||||
 | 
			
		||||
        spec = Spec('libelf ^libdwarf')
 | 
			
		||||
        self.assertRaises(spack.spec.InvalidDependencyException, spec.normalize)
 | 
			
		||||
 | 
			
		||||
        spec = Spec('mpich ^dyninst ^libelf')
 | 
			
		||||
        self.assertRaises(spack.spec.InvalidDependencyException, spec.normalize)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def test_equal(self):
 | 
			
		||||
        spec = Spec('mpileaks ^callpath ^libelf ^libdwarf')
 | 
			
		||||
        self.assertNotEqual(spec, Spec(
 | 
			
		||||
            'mpileaks', Spec('callpath',
 | 
			
		||||
                             Spec('libdwarf',
 | 
			
		||||
                                  Spec('libelf')))))
 | 
			
		||||
        self.assertNotEqual(spec, Spec(
 | 
			
		||||
            'mpileaks', Spec('callpath',
 | 
			
		||||
                             Spec('libelf',
 | 
			
		||||
                                  Spec('libdwarf')))))
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(spec, Spec(
 | 
			
		||||
            'mpileaks', Spec('callpath'), Spec('libdwarf'), Spec('libelf')))
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(spec, Spec(
 | 
			
		||||
            'mpileaks', Spec('libelf'), Spec('libdwarf'), Spec('callpath')))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def test_normalize_mpileaks(self):
 | 
			
		||||
        spec = Spec('mpileaks ^mpich ^callpath ^dyninst ^libelf@1.8.11 ^libdwarf')
 | 
			
		||||
 | 
			
		||||
        expected_flat = Spec(
 | 
			
		||||
            'mpileaks', Spec('mpich'), Spec('callpath'), Spec('dyninst'),
 | 
			
		||||
            Spec('libelf@1.8.11'), Spec('libdwarf'))
 | 
			
		||||
 | 
			
		||||
        mpich = Spec('mpich')
 | 
			
		||||
        libelf = Spec('libelf@1.8.11')
 | 
			
		||||
        expected_normalized = Spec(
 | 
			
		||||
            'mpileaks',
 | 
			
		||||
            Spec('callpath',
 | 
			
		||||
                 Spec('dyninst', Spec('libdwarf', libelf),
 | 
			
		||||
                      libelf),
 | 
			
		||||
                 mpich), mpich)
 | 
			
		||||
 | 
			
		||||
        expected_non_dag = Spec(
 | 
			
		||||
            'mpileaks',
 | 
			
		||||
            Spec('callpath',
 | 
			
		||||
                 Spec('dyninst', Spec('libdwarf', Spec('libelf@1.8.11')),
 | 
			
		||||
                      Spec('libelf@1.8.11')),
 | 
			
		||||
                 mpich), Spec('mpich'))
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(expected_normalized, expected_non_dag)
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(str(expected_normalized), str(expected_non_dag))
 | 
			
		||||
        self.assertEqual(str(spec), str(expected_non_dag))
 | 
			
		||||
        self.assertEqual(str(expected_normalized), str(spec))
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(spec, expected_flat)
 | 
			
		||||
        self.assertNotEqual(spec, expected_normalized)
 | 
			
		||||
        self.assertNotEqual(spec, expected_non_dag)
 | 
			
		||||
 | 
			
		||||
        spec.normalize()
 | 
			
		||||
 | 
			
		||||
        self.assertNotEqual(spec, expected_flat)
 | 
			
		||||
        self.assertEqual(spec, expected_normalized)
 | 
			
		||||
        self.assertEqual(spec, expected_non_dag)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user