Repository: spack/spack

Issue:
Unsatisfiable dependency constraint with no installed packages
On a clean clone of spack (dec6b609d), the following command fails to complete:

```
$ spack install py-matplotlib@2.0.0
==> Error: An unsatisfiable dependency constraint has been detected for spec:

    py-pillow@3.2.0%gcc@7.1.1~freetype+jpeg~jpeg2000~lcms~tiff+zlib arch=linux-arch-x86_64 
        ^binutils@2.28%gcc@7.1.1+gold~libiberty~plugins arch=linux-arch-x86_64 
            ^bison@3.0.4%gcc@7.1.1 arch=linux-arch-x86_64 
                ^m4@1.4.18%gcc@7.1.1+sigsegv arch=linux-arch-x86_64 
                    ^libsigsegv
            ^flex@2.6.4%gcc@7.1.1+lex arch=linux-arch-x86_64 
                ^gettext@0.19.8.1%gcc@7.1.1+bzip2+curses+git~libunistring+libxml2+tar+xz arch=linux-arch-x86_64 
                    ^bzip2@1.0.6%gcc@7.1.1+shared arch=linux-arch-x86_64 
                    ^libxml2
                        ^pkg-config@0.29.2%gcc@7.1.1+internal_glib arch=linux-arch-x86_64 
                        ^xz
                        ^zlib@1.2.11%gcc@7.1.1+pic+shared arch=linux-arch-x86_64 
                    ^ncurses@6.0%gcc@7.1.1~symlinks arch=linux-arch-x86_64 
                    ^tar
                ^help2man@1.47.4%gcc@7.1.1 arch=linux-arch-x86_64 
        ^libjpeg-turbo@1.5.0%gcc@7.1.1 arch=linux-arch-x86_64 
            ^nasm
        ^py-setuptools@35.0.2%gcc@7.1.1 arch=linux-arch-x86_64 
            ^py-appdirs@1.4.3%gcc@7.1.1 arch=linux-arch-x86_64 
                ^python@2.7.13%gcc@7.1.1+shared~tk~ucs4 arch=linux-arch-x86_64 
                    ^openssl@1.0.2k%gcc@7.1.1 arch=linux-arch-x86_64 
                    ^readline@7.0%gcc@7.1.1 arch=linux-arch-x86_64 
                    ^sqlite@3.20.0%gcc@7.1.1 arch=linux-arch-x86_64 
            ^py-packaging@16.8%gcc@7.1.1 arch=linux-arch-x86_64 
                ^py-pyparsing@2.2.0%gcc@7.1.1 arch=linux-arch-x86_64 
                ^py-six@1.10.0%gcc@7.1.1 arch=linux-arch-x86_64 


while trying to concretize the partial spec:

    py-matplotlib@2.0.0%gcc@7.1.1~animation+image~ipython~latex~qt+tk arch=linux-arch-x86_64 
        ^freetype@2.7.1%gcc@7.1.1 arch=linux-arch-x86_64 
            ^bzip2@1.0.6%gcc@7.1.1+shared arch=linux-arch-x86_64 
            ^libpng@1.6.29%gcc@7.1.1 arch=linux-arch-x86_64 
                ^zlib@1.2.11%gcc@7.1.1+pic+shared arch=linux-arch-x86_64 
            ^pkg-config@0.29.2%gcc@7.1.1+internal_glib arch=linux-arch-x86_64 
        ^py-cycler@0.10.0%gcc@7.1.1 arch=linux-arch-x86_64 
            ^py-setuptools@35.0.2%gcc@7.1.1 arch=linux-arch-x86_64 
                ^py-appdirs@1.4.3%gcc@7.1.1 arch=linux-arch-x86_64 
                    ^python@2.7.13%gcc@7.1.1+shared~tk~ucs4 arch=linux-arch-x86_64 
                        ^ncurses@6.0%gcc@7.1.1~symlinks arch=linux-arch-x86_64 
                        ^openssl@1.0.2k%gcc@7.1.1 arch=linux-arch-x86_64 
                        ^readline@7.0%gcc@7.1.1 arch=linux-arch-x86_64 
                        ^sqlite@3.20.0%gcc@7.1.1 arch=linux-arch-x86_64 
                ^py-packaging@16.8%gcc@7.1.1 arch=linux-arch-x86_64 
                    ^py-pyparsing@2.2.0%gcc@7.1.1 arch=linux-arch-x86_64 
                    ^py-six@1.10.0%gcc@7.1.1 arch=linux-arch-x86_64 
        ^py-dateutil@2.5.2%gcc@7.1.1 arch=linux-arch-x86_64 
        ^py-functools32@3.2.3-2%gcc@7.1.1 arch=linux-arch-x86_64 
        ^py-numpy@1.13.1%gcc@7.1.1+blas+lapack arch=linux-arch-x86_64 
            ^openblas@0.2.20%gcc@7.1.1~openmp+pic+shared arch=linux-arch-x86_64 
        ^py-pillow@3.2.0%gcc@7.1.1~freetype+jpeg~jpeg2000~lcms~tiff+zlib arch=linux-arch-x86_64 
            ^binutils@2.28%gcc@7.1.1+gold~libiberty~plugins arch=linux-arch-x86_64 
                ^bison@3.0.4%gcc@7.1.1 arch=linux-arch-x86_64 
                    ^m4@1.4.18%gcc@7.1.1+sigsegv arch=linux-arch-x86_64 
                        ^libsigsegv
                ^flex@2.6.4%gcc@7.1.1+lex arch=linux-arch-x86_64 
                    ^gettext@0.19.8.1%gcc@7.1.1+bzip2+curses+git~libunistring+libxml2+tar+xz arch=linux-arch-x86_64 
                        ^libxml2
                            ^xz
                        ^tar
                    ^help2man@1.47.4%gcc@7.1.1 arch=linux-arch-x86_64 
            ^libjpeg-turbo@1.5.0%gcc@7.1.1 arch=linux-arch-x86_64 
                ^nasm
        ^py-pytz@2017.2%gcc@7.1.1 arch=linux-arch-x86_64 
        ^py-subprocess32@3.2.7%gcc@7.1.1 arch=linux-arch-x86_64 
        ^qhull@2015.2%gcc@7.1.1 build_type=RelWithDebInfo arch=linux-arch-x86_64 
            ^cmake@3.9.0%gcc@7.1.1~doc+ncurses+openssl+ownlibs~qt arch=linux-arch-x86_64 
        ^tk@8.6.6%gcc@7.1.1 arch=linux-arch-x86_64 
            ^libx11@1.6.5%gcc@7.1.1 arch=linux-arch-x86_64 
                ^inputproto@2.3.2%gcc@7.1.1 arch=linux-arch-x86_64 
                    ^util-macros@1.19.1%gcc@7.1.1 arch=linux-arch-x86_64 
                ^kbproto@1.0.7%gcc@7.1.1 arch=linux-arch-x86_64 
                ^libxcb@1.12%gcc@7.1.1 arch=linux-arch-x86_64 
                    ^libpthread-stubs@0.4%gcc@7.1.1 arch=linux-arch-x86_64 
                    ^libxau@1.0.8%gcc@7.1.1 arch=linux-arch-x86_64 
                        ^xproto@7.0.31%gcc@7.1.1 arch=linux-arch-x86_64 
                    ^libxdmcp@1.1.2%gcc@7.1.1 arch=linux-arch-x86_64 
                    ^xcb-proto@1.12%gcc@7.1.1 arch=linux-arch-x86_64 
                ^xextproto@7.3.0%gcc@7.1.1 arch=linux-arch-x86_64 
                ^xtrans@1.3.5%gcc@7.1.1 arch=linux-arch-x86_64 
            ^tcl@8.6.6%gcc@7.1.1 arch=linux-arch-x86_64 


py-matplotlib requires py-pillow dependency py-pillow@3.2.0%gcc@7.1.1~freetype+jpeg~jpeg2000~lcms~tiff+zlib arch=linux-arch-x86_64 ^binutils@2.28%gcc@7.1.1+gold~libiberty~plugins arch=linux-arch-x86_64 ^bison@3.0.4%gcc@7.1.1 arch=linux-arch-x86_64 ^bzip2@1.0.6%gcc@7.1.1+shared arch=linux-arch-x86_64 ^flex@2.6.4%gcc@7.1.1+lex arch=linux-arch-x86_64 ^gettext@0.19.8.1%gcc@7.1.1+bzip2+curses+git~libunistring+libxml2+tar+xz arch=linux-arch-x86_64 ^help2man@1.47.4%gcc@7.1.1 arch=linux-arch-x86_64 ^libjpeg-turbo@1.5.0%gcc@7.1.1 arch=linux-arch-x86_64 ^libsigsegv^libxml2^m4@1.4.18%gcc@7.1.1+sigsegv arch=linux-arch-x86_64 ^nasm^ncurses@6.0%gcc@7.1.1~symlinks arch=linux-arch-x86_64 ^openssl@1.0.2k%gcc@7.1.1 arch=linux-arch-x86_64 ^pkg-config@0.29.2%gcc@7.1.1+internal_glib arch=linux-arch-x86_64 ^py-appdirs@1.4.3%gcc@7.1.1 arch=linux-arch-x86_64 ^py-packaging@16.8%gcc@7.1.1 arch=linux-arch-x86_64 ^py-pyparsing@2.2.0%gcc@7.1.1 arch=linux-arch-x86_64 ^py-setuptools@35.0.2%gcc@7.1.1 arch=linux-arch-x86_64 ^py-six@1.10.0%gcc@7.1.1 arch=linux-arch-x86_64 ^python@2.7.13%gcc@7.1.1+shared~tk~ucs4 arch=linux-arch-x86_64 ^readline@7.0%gcc@7.1.1 arch=linux-arch-x86_64 ^sqlite@3.20.0%gcc@7.1.1 arch=linux-arch-x86_64 ^tar^xz^zlib@1.2.11%gcc@7.1.1+pic+shared arch=linux-arch-x86_64, but spec asked for py-pillow@3.2.0%gcc@7.1.1~freetype+jpeg~jpeg2000~lcms~tiff+zlib arch=linux-arch-x86_64 ^binutils@2.28%gcc@7.1.1+gold~libiberty~plugins arch=linux-arch-x86_64 ^bison@3.0.4%gcc@7.1.1 arch=linux-arch-x86_64 ^bzip2@1.0.6%gcc@7.1.1+shared arch=linux-arch-x86_64 ^flex@2.6.4%gcc@7.1.1+lex arch=linux-arch-x86_64 ^gettext@0.19.8.1%gcc@7.1.1+bzip2+curses+git~libunistring+libxml2+tar+xz arch=linux-arch-x86_64 ^help2man@1.47.4%gcc@7.1.1 arch=linux-arch-x86_64 ^libjpeg-turbo@1.5.0%gcc@7.1.1 arch=linux-arch-x86_64 ^libsigsegv^libxml2^m4@1.4.18%gcc@7.1.1+sigsegv arch=linux-arch-x86_64 ^nasm^ncurses@6.0%gcc@7.1.1~symlinks arch=linux-arch-x86_64 ^openssl@1.0.2k%gcc@7.1.1 arch=linux-arch-x86_64 ^pkg-config@0.29.2%gcc@7.1.1+internal_glib arch=linux-arch-x86_64 ^py-appdirs@1.4.3%gcc@7.1.1 arch=linux-arch-x86_64 ^py-packaging@16.8%gcc@7.1.1 arch=linux-arch-x86_64 ^py-pyparsing@2.2.0%gcc@7.1.1 arch=linux-arch-x86_64 ^py-setuptools@35.0.2%gcc@7.1.1 arch=linux-arch-x86_64 ^py-six@1.10.0%gcc@7.1.1 arch=linux-arch-x86_64 ^python@2.7.13%gcc@7.1.1+shared~tk~ucs4 arch=linux-arch-x86_64 ^readline@7.0%gcc@7.1.1 arch=linux-arch-x86_64 ^sqlite@3.20.0%gcc@7.1.1 arch=linux-arch-x86_64 ^tar^xz^zlib@1.2.11%gcc@7.1.1+pic+shared arch=linux-arch-x86_64
```

If you diff both required and "asked for" spec they are completely identical..


Discussion:
This must have broken in the last 2 weeks or so, as of ```be0e051 Add version 1.7.1 for cub. (#5164)``` this still worked

#5190 is the culprit. Although I don't quite understand why yet...
@tgamblin is looking at this

Resolving patch:
diff --git a/lib/spack/spack/binary_distribution.py b/lib/spack/spack/binary_distribution.py
--- a/lib/spack/spack/binary_distribution.py
+++ b/lib/spack/spack/binary_distribution.py
@@ -447,7 +447,12 @@ def get_specs():
                 except fs.FetchError:
                     continue
                 with open(stage.save_filename, 'r') as f:
+                    # read the spec from the build cache file. All specs
+                    # in build caches are concrete (as they aer built) so
+                    # we need to mark this spec concrete on read-in.
                     spec = spack.spec.Spec.from_yaml(f)
+                    spec._mark_concrete()
+
                     specs.add(spec)
                     durls[spec].append(link)
     return specs, durls
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -635,10 +635,6 @@ def constrain(self, other):
     def valid_compiler_flags():
         return _valid_compiler_flags
 
-    @property
-    def concrete(self):
-        return all(flag in self for flag in _valid_compiler_flags)
-
     def copy(self):
         clone = FlagMap(None)
         for name, value in self.items():
@@ -661,73 +657,6 @@ class DependencyMap(HashableMap):
     """Each spec has a DependencyMap containing specs for its dependencies.
        The DependencyMap is keyed by name. """
 
-    def __init__(self, owner):
-        super(DependencyMap, self).__init__()
-
-        # Owner Spec for the current map
-        self.owner = owner
-
-    @property
-    def concrete(self):
-
-        # Check if the dependencies are concrete and of the correct type
-        dependencies_are_concrete = all(
-            (d.spec.concrete and d.deptypes) for d in self.values()
-        )
-
-        if not dependencies_are_concrete:
-            return False
-
-        # If we are dealing with an external spec, it clearly has no
-        # dependencies managed by spack
-        if self.owner.external:
-            return True
-
-        # Now check we have every dependency we need
-        pkg = self.owner.package
-        for dependency in pkg.dependencies.values():
-            # Check that for every condition we satisfy we satisfy also
-            # the associated constraint
-            for condition, constraint in dependency.items():
-                # WARNING: This part relies on the fact that dependencies are
-                # the last item to be checked when computing if a spec is
-                # concrete. This means that we are ensured that all variants,
-                # versions, compilers, etc. are there with their final value
-                # when we call 'Spec.satisfies(..., strict=True)'
-
-                # FIXME: at the moment check only for non conditional
-                # FIXME: dependencies, to avoid infinite recursion
-
-                # satisfy_condition = self.owner.satisfies(
-                #     condition, strict=True
-                # )
-
-                satisfy_condition = str(condition) == self.owner.name
-
-                if not satisfy_condition:
-                    continue
-
-                dependency_name = constraint.name
-
-                # We don't want to check build dependencies
-                if pkg.dependency_types[dependency_name] == set(['build']):
-                    continue
-
-                try:
-                    dependency = self.owner[dependency_name]
-                    satisfy_constraint = dependency.satisfies(
-                        constraint, strict=True
-                    )
-                except KeyError:
-                    # If the dependency is not there we don't
-                    # satisfy the constraint
-                    satisfy_constraint = False
-
-                if satisfy_condition and not satisfy_constraint:
-                    return False
-
-        return True
-
     def __str__(self):
         return "{deps: %s}" % ', '.join(str(d) for d in sorted(self.values()))
 
@@ -1315,26 +1244,16 @@ def is_virtual(name):
 
     @property
     def concrete(self):
-        """A spec is concrete if it can describe only ONE build of a package.
-           If any of the name, version, architecture, compiler,
-           variants, or depdenencies are ambiguous,then it is not concrete.
-        """
-        # If we have cached that the spec is concrete, then it's concrete
-        if self._concrete:
-            return True
+        """A spec is concrete if it describes a single build of a package.
 
-        # Otherwise check if the various parts of the spec are concrete.
-        # It's crucial to have the check on dependencies last, as it relies
-        # on all the other parts to be already checked for concreteness.
-        self._concrete = bool(not self.virtual and
-                              self.namespace is not None and
-                              self.versions.concrete and
-                              self.variants.concrete and
-                              self.architecture and
-                              self.architecture.concrete and
-                              self.compiler and self.compiler.concrete and
-                              self.compiler_flags.concrete and
-                              self._dependencies.concrete)
+        More formally, a spec is concrete if concretize() has been called
+        on it and it has been marked `_concrete`.
+
+        Concrete specs either can be or have been built. All constraints
+        have been resolved, optional dependencies have been added or
+        removed, a compiler has been chosen, and all variants have
+        values.
+        """
         return self._concrete
 
     def traverse(self, **kwargs):
@@ -1825,8 +1744,8 @@ def _expand_virtual_packages(self):
                 if replacement.external:
                     if (spec._dependencies):
                         changed = True
-                        spec._dependencies = DependencyMap(spec)
-                    replacement._dependencies = DependencyMap(replacement)
+                        spec._dependencies = DependencyMap()
+                    replacement._dependencies = DependencyMap()
                     replacement.architecture = self.architecture
 
                 # TODO: could this and the stuff in _dup be cleaned up?
@@ -1986,7 +1905,7 @@ def flat_dependencies(self, **kwargs):
     def index(self, deptype=None):
         """Return DependencyMap that points to all the dependencies in this
            spec."""
-        dm = DependencyMap(None)
+        dm = DependencyMap()
         for spec in self.traverse(deptype=deptype):
             dm[spec.name] = spec
         return dm
@@ -2113,14 +2032,15 @@ def _merge_dependency(self, dep, deptypes, visited, spec_deps,
                 fmt += 'while trying to concretize the partial spec:'
                 fmt += '\n\n{0}\n\n'.format(self.tree(indent=4))
                 fmt += '{0} requires {1} {2} {3}, but spec asked for {4}'
+
                 e.message = fmt.format(
                     self.name,
                     dep.name,
                     e.constraint_type,
                     e.required,
-                    e.provided
-                )
-                raise e
+                    e.provided)
+
+                raise
 
         # Add merged spec to my deps and recurse
         dependency = spec_deps[dep.name]
@@ -2182,13 +2102,13 @@ def normalize(self, force=False):
         if not self.name:
             raise SpecError("Attempting to normalize anonymous spec")
 
-        if self._normal and not force:
-            return False
-
-        # avoid any assumptions about concreteness when forced
+        # Set _normal and _concrete to False when forced
         if force:
             self._mark_concrete(False)
 
+        if self._normal:
+            return False
+
         # Ensure first that all packages & compilers in the DAG exist.
         self.validate_or_raise()
         # Get all the dependencies into one DependencyMap
@@ -2541,7 +2461,7 @@ def virtual_dependencies(self):
         """Return list of any virtual deps in this spec."""
         return [spec for spec in self.traverse() if spec.virtual]
 
-    def _dup(self, other, deps=True, cleardeps=True):
+    def _dup(self, other, deps=True, cleardeps=True, caches=None):
         """Copy the spec other into self.  This is an overwriting
         copy.  It does not copy any dependents (parents), but by default
         copies dependencies.
@@ -2556,6 +2476,10 @@ def _dup(self, other, deps=True, cleardeps=True):
             cleardeps (bool): if True clears the dependencies of ``self``,
                 before possibly copying the dependencies of ``other`` onto
                 ``self``
+            caches (bool or None): preserve cached fields such as
+                ``_normal``, ``_concrete``, and ``_cmp_key_cache``. By
+                default this is ``False`` if DAG structure would be
+                changed by the copy, ``True`` if it's an exact copy.
 
         Returns:
             True if ``self`` changed because of the copy operation,
@@ -2583,8 +2507,8 @@ def _dup(self, other, deps=True, cleardeps=True):
             else None
         self.compiler = other.compiler.copy() if other.compiler else None
         if cleardeps:
-            self._dependents = DependencyMap(self)
-            self._dependencies = DependencyMap(self)
+            self._dependents = DependencyMap()
+            self._dependencies = DependencyMap()
         self.compiler_flags = other.compiler_flags.copy()
         self.compiler_flags.spec = self
         self.variants = other.variants.copy()
@@ -2593,21 +2517,20 @@ def _dup(self, other, deps=True, cleardeps=True):
         self.external_module = other.external_module
         self.namespace = other.namespace
 
+        # Cached fields are results of expensive operations.
+        # If we preserved the original structure, we can copy them
+        # safely. If not, they need to be recomputed.
+        if caches is None:
+            caches = (deps is True or deps == alldeps)
+
         # If we copy dependencies, preserve DAG structure in the new spec
         if deps:
-            deptypes = alldeps  # by default copy all deptypes
-
-            # if caller restricted deptypes to be copied, adjust that here.
-            if isinstance(deps, (tuple, list)):
-                deptypes = deps
-
-            self._dup_deps(other, deptypes)
+            # If caller restricted deptypes to be copied, adjust that here.
+            # By default, just copy all deptypes
+            deptypes = deps if isinstance(deps, (tuple, list)) else alldeps
+            self._dup_deps(other, deptypes, caches)
 
-        # These fields are all cached results of expensive operations.
-        # If we preserved the original structure, we can copy them
-        # safely. If not, they need to be recomputed.
-        # TODO: dependency hashes can be copied more aggressively.
-        if deps is True or deps == alldeps:
+        if caches:
             self._hash = other._hash
             self._cmp_key_cache = other._cmp_key_cache
             self._normal = other._normal
@@ -2620,7 +2543,7 @@ def _dup(self, other, deps=True, cleardeps=True):
 
         return changed
 
-    def _dup_deps(self, other, deptypes):
+    def _dup_deps(self, other, deptypes, caches):
         new_specs = {self.name: self}
         for dspec in other.traverse_edges(cover='edges', root=False):
             if (dspec.deptypes and
@@ -2628,29 +2551,45 @@ def _dup_deps(self, other, deptypes):
                 continue
 
             if dspec.parent.name not in new_specs:
-                new_specs[dspec.parent.name] = dspec.parent.copy(deps=False)
+                new_specs[dspec.parent.name] = dspec.parent.copy(
+                    deps=False, caches=caches)
             if dspec.spec.name not in new_specs:
-                new_specs[dspec.spec.name] = dspec.spec.copy(deps=False)
+                new_specs[dspec.spec.name] = dspec.spec.copy(
+                    deps=False, caches=caches)
 
             new_specs[dspec.parent.name]._add_dependency(
                 new_specs[dspec.spec.name], dspec.deptypes)
 
-    def copy(self, deps=True):
-        """Return a copy of this spec.
+    def copy(self, deps=True, **kwargs):
+        """Make a copy of this spec.
+
+        Args:
+            deps (bool or tuple): Defaults to True. If boolean, controls
+                whether dependencies are copied (copied if True). If a
+                tuple is provided, *only* dependencies of types matching
+                those in the tuple are copied.
+            kwargs: additional arguments for internal use (passed to ``_dup``).
+
+        Returns:
+            A copy of this spec.
+
+        Examples:
+            Deep copy with dependnecies::
+
+                spec.copy()
+                spec.copy(deps=True)
 
-        By default, returns a deep copy. To control how dependencies are
-        copied, supply:
+            Shallow copy (no dependencies)::
 
-        deps=True:  deep copy
+                spec.copy(deps=False)
 
-        deps=False: shallow copy (no dependencies)
+            Only build and run dependencies::
 
-        deps=('link', 'build'):
-            only build and link dependencies.  Similar for other deptypes.
+                deps=('build', 'run'):
 
         """
         clone = Spec.__new__(Spec)
-        clone._dup(self, deps=deps)
+        clone._dup(self, deps=deps, **kwargs)
         return clone
 
     @property
@@ -3276,8 +3215,8 @@ def spec(self, name):
         spec.external_path = None
         spec.external_module = None
         spec.compiler_flags = FlagMap(spec)
-        spec._dependents = DependencyMap(spec)
-        spec._dependencies = DependencyMap(spec)
+        spec._dependents = DependencyMap()
+        spec._dependencies = DependencyMap()
         spec.namespace = spec_namespace
         spec._hash = None
         spec._cmp_key_cache = None


Test patch:
diff --git a/lib/spack/spack/test/directory_layout.py b/lib/spack/spack/test/directory_layout.py
--- a/lib/spack/spack/test/directory_layout.py
+++ b/lib/spack/spack/test/directory_layout.py
@@ -55,8 +55,10 @@ def test_yaml_directory_layout_parameters(
     # Ensure default layout matches expected spec format
     layout_default = YamlDirectoryLayout(str(tmpdir))
     path_default = layout_default.relative_path_for_spec(spec)
-    assert(path_default ==
-           spec.format("${ARCHITECTURE}/${COMPILERNAME}-${COMPILERVER}/${PACKAGE}-${VERSION}-${HASH}"))   # NOQA: ignore=E501
+    assert(path_default == spec.format(
+        "${ARCHITECTURE}/"
+        "${COMPILERNAME}-${COMPILERVER}/"
+        "${PACKAGE}-${VERSION}-${HASH}"))
 
     # Test hash_length parameter works correctly
     layout_10 = YamlDirectoryLayout(str(tmpdir), hash_len=10)
@@ -133,6 +135,8 @@ def test_read_and_write_spec(
         # TODO: increase reuse of build dependencies.
         stored_deptypes = ('link', 'run')
         expected = spec.copy(deps=stored_deptypes)
+        expected._mark_concrete()
+
         assert expected.concrete
         assert expected == spec_from_file
         assert expected.eq_dag(spec_from_file)