summaryrefslogblamecommitdiff
path: root/lib/spack/spack/test/spec_dag.py
blob: 3a9c0350ae48a94061366accb6af452fc725a82d (plain) (tree)
1
2
3
4
5
6
7
8
9
                                                                         
                                                                         
 
                                              
   
                                                           
   
             
 
                           
                  
                         
                   
                 
                                  
                                       
                           
 
 


                                           
                                                                  
 
                                              
                                                                   








                                                        
                                            
                                                                
                                                
       
 
                              





                                                                            
                                                         
                                      
                                                                         
 
                                 
                                              
                                                                                
 


                
                                  
                              
                                                                         
                                        
 




             
 

                                                                      







                                                                                   

                              

 
                                  
                                                                               

                                                                              
 

                                                                                     
 

                                               
 
                                          
 
       















                                                                   
                                                          





                                                         
                                                           




                                                                          
                                                                           
                       



                                                             
 
 
                                  
                                                       


                                                                             



                                                              
 


                                                                                 
 
                          
                                                                               

                           
                                                                             

 




                                                                       
                                                                                     





                                                                              







                                                                      

 
                                                       
                  
                                                                   

                                               
 
                                                                           
 
                                                                   
                                                                 

                                                                     
 
                                           
                                    

                       
                                                                                         
                                                       
 
                                  
                                                   
 
                                            
                                                           
 
                                           
                                    
                       
 










                       
                                                             
 
                                               
                                                   
 
                                                           
                                                           
 
                                           
                                    

                       











                       
                                                                
 
                                               
                                                   
 
                                                           
                                                           

                                            
                                    

                       
                                                                                         
                                                       
 
                                              
                                                   
 
                                                          
                                                           

                                            
                                    

                       










                       
                                                             
 
                                                             
                                                   
 
                                                                         
                                                           

                                            
                                    

                       











                       
                                                                
 
                                                             
                                                   
 
                                                                         
                                                           
 
                                                
                                                                               



                                                                           
 
                                                                                
 


                                                                                      
 

                                                             
 

                                                                 

                                     



                        
                         
 
                                   
                               




                        
                                               





                                                                                  
                      

                                
             
         



                                                           
                                         
                                       



                                     
                                    

                                                           





                                                                                  
                      

                                
             
         
 
                         
                        
                         
 
                                                         

                                                                               

                                                                     
 
                                                          

                                                                                    

                                                                      
 
                                                                  

                                                                                      

                                                                      
 
                                                              



                                                                                             

                                                                          
 


                                                                                   


                                                  
                             

                         
                                                        



                                                                                

         


                                                                              
 


                                                                                                



                                                           

                                       

                                                        
                                     
 
                                                                     
                                     
 
                                                        




                                             
 
                                      
                                      


                                                                                 
 
                                                       








                                          
             
         
 
                                                      







                                                                              
                      
                                
                 

             
 


                                                                      





                                                                                                
                      




                                
 


                                                                            

                                       
 
                                                                 

                                         
 
                                                               

                                                   
 
                                                      

                                                


                        
                                                                             

                                             
 


                                                                
 

                                                                  
 
                                                              
                                                  
                                                             

                        





                                                                                                
                      

                                
             
         
 
                                                    
 
                            







                                                             
 
                               
                               
                          
                         
 



                                               



                                                      
                                                  
 
                                   
                               

                          
                         
 

                                



                                                      
                                                  
 
                                    
                               


                          
                         
 



                                               



                                                      
                                                  
 



                                                                                     
                                          




                            
                                 


                            
                                             

                                  
























                                                                            
 
                                     
                           

                       











                                                           
                                                   

                                                    
                           

                       
                                                                                             
 
                                                           
                                                   

                                          
                           

                       













                       
 
                                             
                                                   
 
                                         
                           

                       
                                             
 
                                               
                                                   
 



                                                                             

                                                                              


                             






                                               


                                                              
                                                                                             

                                                                     
                                                                         
                                          
                                                                  
 
                                                
 
                                           
                                                           
 
                                           
                                                           


                                                                  
                                                        


                                                                                
 


                                                               
 
                                                                                           
 


                                                                   


                                                                  
                                                        


                                                                                
 


                                                               
 
                                                                                           
 


                                                                   

                                     
                                         

                      

                                  

                                                
                                                                       




                                                                                   


                                                                                      
 


                                                                                      

                                           





                                                                         


                                                                                       
         










                                                                                               
         

                                                 





                                                                                              
             
         
 



                                                                               

                                             






                                                                              
                              




                                                                      
                               







                                             
                               




                                       

                                 
                            


                                                
                         

                            
                                       



                                               
                    

                            
                                  




                                                 
                                

                            
                                  
                                               

                                                  


                                             
                            

                                      
                         


                                              
                          


                                           


                                     

                                               
 
                                       
                                 
                                       
                                   
 


                                                               

                



                                                                                       

               


                                                                                       

                 







                                                                                       


                                          
                                    
                                       
                                     


                                       
                                  
                                       
                                           
                                       
                                     

                                        
                                                     
                                                         
                                                  


                                               
                                                                


                                                                       




                                                   

 
                                                                                               








                                                                  
                                  

                                                
 


                                                                         


                                                     
                                                          
 

                                                                             














                                                                     

                                            
 
                                                                      

                                        

                                             









                                                                                 

                                            
 

                                                                                 


                                                     

                                                                             




                                                          

                                                                                   



                                                      
                         

                                                                            
 
                                                       
                                                 
  


                                     
 
                                                              
                                               
                                                                  

















                                                                                      
                                                                                  

                                                           

                                                                    

                                                    


                                                                     






                                                
# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
"""
These tests check Spec DAG operations using dummy packages.
"""
import pytest

import spack.deptypes as dt
import spack.error
import spack.package_base
import spack.parser
import spack.repo
import spack.util.hash as hashutil
from spack.dependency import Dependency
from spack.spec import Spec


def check_links(spec_to_check):
    for spec in spec_to_check.traverse():
        for dependent in spec.dependents():
            assert dependent.edges_to_dependencies(name=spec.name)

        for dependency in spec.dependencies():
            assert dependency.edges_from_dependents(name=spec.name)


@pytest.fixture()
def saved_deps():
    """Returns a dictionary to save the dependencies."""
    return {}


@pytest.fixture()
def set_dependency(saved_deps, monkeypatch):
    """Returns a function that alters the dependency information
    for a package in the ``saved_deps`` fixture.
    """

    def _mock(pkg_name, spec):
        """Alters dependence information for a package.

        Adds a dependency on <spec> to pkg. Use this to mock up constraints.
        """
        spec = Spec(spec)
        # Save original dependencies before making any changes.
        pkg_cls = spack.repo.PATH.get_pkg_class(pkg_name)
        if pkg_name not in saved_deps:
            saved_deps[pkg_name] = (pkg_cls, pkg_cls.dependencies.copy())

        cond = Spec(pkg_cls.name)
        dependency = Dependency(pkg_cls, spec)
        monkeypatch.setitem(pkg_cls.dependencies, spec.name, {cond: dependency})

    return _mock


@pytest.mark.usefixtures("config")
def test_test_deptype(tmpdir):
    """Ensure that test-only dependencies are only included for specified
    packages in the following spec DAG::

            w
           /|
          x y
            |
            z

    w->y deptypes are (link, build), w->x and y->z deptypes are (test)
    """
    builder = spack.repo.MockRepositoryBuilder(tmpdir)
    builder.add_package("x")
    builder.add_package("z")
    builder.add_package("y", dependencies=[("z", "test", None)])
    builder.add_package("w", dependencies=[("x", "test", None), ("y", None, None)])

    with spack.repo.use_repositories(builder.root):
        spec = Spec("w").concretized(tests=("w",))
        assert "x" in spec
        assert "z" not in spec


@pytest.mark.usefixtures("config")
@pytest.mark.only_clingo("fails with the original concretizer and full hashes")
def test_installed_deps(monkeypatch, mock_packages):
    """Ensure that concrete specs and their build deps don't constrain solves.

    Preinstall a package ``c`` that has a constrained build dependency on ``d``, then
    install ``a`` and ensure that neither:

      * ``c``'s package constraints, nor
      * the concrete ``c``'s build dependencies

    constrain ``a``'s dependency on ``d``.

    """
    # see installed-deps-[abcde] test packages.
    #     a
    #    / \
    #   b   c   b --> d build/link
    #   |\ /|   b --> e build/link
    #   |/ \|   c --> d build
    #   d   e   c --> e build/link
    #
    a, b, c, d, e = ["installed-deps-%s" % s for s in "abcde"]

    # install C, which will force d's version to be 2
    # BUT d is only a build dependency of C, so it won't constrain
    # link/run dependents of C when C is depended on as an existing
    # (concrete) installation.
    c_spec = Spec(c)
    c_spec.concretize()
    assert c_spec[d].version == spack.version.Version("2")

    installed_names = [s.name for s in c_spec.traverse()]

    def _mock_installed(self):
        return self.name in installed_names

    monkeypatch.setattr(Spec, "installed", _mock_installed)

    # install A, which depends on B, C, D, and E, and force A to
    # use the installed C.  It should *not* force A to use the installed D
    # *if* we're doing a fresh installation.
    a_spec = Spec(a)
    a_spec._add_dependency(c_spec, depflag=dt.BUILD | dt.LINK, virtuals=())
    a_spec.concretize()
    assert spack.version.Version("2") == a_spec[c][d].version
    assert spack.version.Version("2") == a_spec[e].version
    assert spack.version.Version("3") == a_spec[b][d].version
    assert spack.version.Version("3") == a_spec[d].version


@pytest.mark.usefixtures("config")
def test_specify_preinstalled_dep(tmpdir, monkeypatch):
    """Specify the use of a preinstalled package during concretization with a
    transitive dependency that is only supplied by the preinstalled package.
    """
    builder = spack.repo.MockRepositoryBuilder(tmpdir)
    builder.add_package("c")
    builder.add_package("b", dependencies=[("c", None, None)])
    builder.add_package("a", dependencies=[("b", None, None)])

    with spack.repo.use_repositories(builder.root):
        b_spec = Spec("b").concretized()
        monkeypatch.setattr(Spec, "installed", property(lambda x: x.name != "a"))

        a_spec = Spec("a")
        a_spec._add_dependency(b_spec, depflag=dt.BUILD | dt.LINK, virtuals=())
        a_spec.concretize()

        assert set(x.name for x in a_spec.traverse()) == set(["a", "b", "c"])


@pytest.mark.usefixtures("config")
@pytest.mark.parametrize(
    "spec_str,expr_str,expected",
    [("x ^y@2", "y@2", True), ("x@1", "y", False), ("x", "y@3", True)],
)
def test_conditional_dep_with_user_constraints(tmpdir, spec_str, expr_str, expected):
    """This sets up packages X->Y such that X depends on Y conditionally. It
    then constructs a Spec with X but with no constraints on X, so that the
    initial normalization pass cannot determine whether the constraints are
    met to add the dependency; this checks whether a user-specified constraint
    on Y is applied properly.
    """
    builder = spack.repo.MockRepositoryBuilder(tmpdir)
    builder.add_package("y")
    builder.add_package("x", dependencies=[("y", None, "x@2:")])

    with spack.repo.use_repositories(builder.root):
        spec = Spec(spec_str).concretized()
        result = expr_str in spec
        assert result is expected, "{0} in {1}".format(expr_str, spec)


@pytest.mark.usefixtures("mutable_mock_repo", "config")
class TestSpecDag:
    def test_conflicting_package_constraints(self, set_dependency):
        set_dependency("mpileaks", "mpich@1.0")
        set_dependency("callpath", "mpich@2.0")

        spec = Spec("mpileaks ^mpich ^callpath ^dyninst ^libelf ^libdwarf")

        # TODO: try to do something to show that the issue was with
        # TODO: the user's input or with package inconsistencies.
        with pytest.raises(spack.spec.UnsatisfiableVersionSpecError):
            spec.normalize()

    def test_preorder_node_traversal(self):
        dag = Spec("mpileaks ^zmpi")
        dag.normalize()

        names = ["mpileaks", "callpath", "dyninst", "libdwarf", "libelf", "zmpi", "fake"]
        pairs = list(zip([0, 1, 2, 3, 4, 2, 3], names))

        traversal = dag.traverse()
        assert [x.name for x in traversal] == names

        traversal = dag.traverse(depth=True)
        assert [(x, y.name) for x, y in traversal] == pairs

    def test_preorder_edge_traversal(self):
        dag = Spec("mpileaks ^zmpi")
        dag.normalize()

        names = [
            "mpileaks",
            "callpath",
            "dyninst",
            "libdwarf",
            "libelf",
            "libelf",
            "zmpi",
            "fake",
            "zmpi",
        ]
        pairs = list(zip([0, 1, 2, 3, 4, 3, 2, 3, 1], names))

        traversal = dag.traverse(cover="edges")
        assert [x.name for x in traversal] == names

        traversal = dag.traverse(cover="edges", depth=True)
        assert [(x, y.name) for x, y in traversal] == pairs

    def test_preorder_path_traversal(self):
        dag = Spec("mpileaks ^zmpi")
        dag.normalize()

        names = [
            "mpileaks",
            "callpath",
            "dyninst",
            "libdwarf",
            "libelf",
            "libelf",
            "zmpi",
            "fake",
            "zmpi",
            "fake",
        ]
        pairs = list(zip([0, 1, 2, 3, 4, 3, 2, 3, 1, 2], names))

        traversal = dag.traverse(cover="paths")
        assert [x.name for x in traversal] == names

        traversal = dag.traverse(cover="paths", depth=True)
        assert [(x, y.name) for x, y in traversal] == pairs

    def test_postorder_node_traversal(self):
        dag = Spec("mpileaks ^zmpi")
        dag.normalize()

        names = ["libelf", "libdwarf", "dyninst", "fake", "zmpi", "callpath", "mpileaks"]
        pairs = list(zip([4, 3, 2, 3, 2, 1, 0], names))

        traversal = dag.traverse(order="post")
        assert [x.name for x in traversal] == names

        traversal = dag.traverse(depth=True, order="post")
        assert [(x, y.name) for x, y in traversal] == pairs

    def test_postorder_edge_traversal(self):
        dag = Spec("mpileaks ^zmpi")
        dag.normalize()

        names = [
            "libelf",
            "libdwarf",
            "libelf",
            "dyninst",
            "fake",
            "zmpi",
            "callpath",
            "zmpi",
            "mpileaks",
        ]
        pairs = list(zip([4, 3, 3, 2, 3, 2, 1, 1, 0], names))

        traversal = dag.traverse(cover="edges", order="post")
        assert [x.name for x in traversal] == names

        traversal = dag.traverse(cover="edges", depth=True, order="post")
        assert [(x, y.name) for x, y in traversal] == pairs

    def test_postorder_path_traversal(self):
        dag = Spec("mpileaks ^zmpi")
        dag.normalize()

        names = [
            "libelf",
            "libdwarf",
            "libelf",
            "dyninst",
            "fake",
            "zmpi",
            "callpath",
            "fake",
            "zmpi",
            "mpileaks",
        ]
        pairs = list(zip([4, 3, 3, 2, 3, 2, 1, 2, 1, 0], names))

        traversal = dag.traverse(cover="paths", order="post")
        assert [x.name for x in traversal] == names

        traversal = dag.traverse(cover="paths", depth=True, order="post")
        assert [(x, y.name) for x, y in traversal] == pairs

    def test_conflicting_spec_constraints(self):
        mpileaks = Spec("mpileaks ^mpich ^callpath ^dyninst ^libelf ^libdwarf")

        # Normalize then add conflicting constraints to the DAG (this is an
        # extremely unlikely scenario, but we test for it anyway)
        mpileaks.normalize()

        mpileaks.edges_to_dependencies(name="mpich")[0].spec = Spec("mpich@1.0")

        mpileaks.edges_to_dependencies(name="callpath")[0].spec.edges_to_dependencies(
            name="mpich"
        )[0].spec = Spec("mpich@2.0")

        with pytest.raises(spack.spec.InconsistentSpecError):
            mpileaks.flat_dependencies(copy=False)

    def test_normalize_twice(self):
        """Make sure normalize can be run twice on the same spec,
        and that it is idempotent."""
        spec = Spec("mpileaks")
        spec.normalize()
        n1 = spec.copy()

        spec.normalize()
        assert n1 == spec

    def test_normalize_a_lot(self):
        spec = Spec("mpileaks")
        spec.normalize()
        spec.normalize()
        spec.normalize()
        spec.normalize()

    def test_normalize_with_virtual_spec(self):
        dag = Spec.from_literal(
            {
                "mpileaks": {
                    "callpath": {
                        "dyninst": {"libdwarf": {"libelf": None}, "libelf": None},
                        "mpi": None,
                    },
                    "mpi": None,
                }
            }
        )
        dag.normalize()

        # make sure nothing with the same name occurs twice
        counts = {}
        for spec in dag.traverse(key=id):
            if spec.name not in counts:
                counts[spec.name] = 0
            counts[spec.name] += 1

        for name in counts:
            assert counts[name] == 1

    def test_dependents_and_dependencies_are_correct(self):
        spec = Spec.from_literal(
            {
                "mpileaks": {
                    "callpath": {
                        "dyninst": {"libdwarf": {"libelf": None}, "libelf": None},
                        "mpi": None,
                    },
                    "mpi": None,
                }
            }
        )

        check_links(spec)
        spec.normalize()
        check_links(spec)

    def test_unsatisfiable_version(self, set_dependency):
        set_dependency("mpileaks", "mpich@1.0")
        spec = Spec("mpileaks ^mpich@2.0 ^callpath ^dyninst ^libelf ^libdwarf")
        with pytest.raises(spack.spec.UnsatisfiableVersionSpecError):
            spec.normalize()

    def test_unsatisfiable_compiler(self, set_dependency):
        set_dependency("mpileaks", "mpich%gcc")
        spec = Spec("mpileaks ^mpich%intel ^callpath ^dyninst ^libelf" " ^libdwarf")
        with pytest.raises(spack.spec.UnsatisfiableCompilerSpecError):
            spec.normalize()

    def test_unsatisfiable_compiler_version(self, set_dependency):
        set_dependency("mpileaks", "mpich%gcc@4.6")
        spec = Spec("mpileaks ^mpich%gcc@4.5 ^callpath ^dyninst ^libelf" " ^libdwarf")
        with pytest.raises(spack.spec.UnsatisfiableCompilerSpecError):
            spec.normalize()

    def test_unsatisfiable_architecture(self, set_dependency):
        set_dependency("mpileaks", "mpich platform=test target=be")
        spec = Spec(
            "mpileaks ^mpich platform=test target=fe ^callpath" " ^dyninst ^libelf ^libdwarf"
        )
        with pytest.raises(spack.spec.UnsatisfiableArchitectureSpecError):
            spec.normalize()

    @pytest.mark.parametrize(
        "spec_str", ["libelf ^mpich", "libelf ^libdwarf", "mpich ^dyninst ^libelf"]
    )
    def test_invalid_dep(self, spec_str):
        spec = Spec(spec_str)
        with pytest.raises(spack.error.SpecError):
            spec.concretize()

    def test_equal(self):
        # Different spec structures to test for equality
        flat = Spec.from_literal({"mpileaks ^callpath ^libelf ^libdwarf": None})

        flat_init = Spec.from_literal(
            {"mpileaks": {"callpath": None, "libdwarf": None, "libelf": None}}
        )

        flip_flat = Spec.from_literal(
            {"mpileaks": {"libelf": None, "libdwarf": None, "callpath": None}}
        )

        dag = Spec.from_literal({"mpileaks": {"callpath": {"libdwarf": {"libelf": None}}}})

        flip_dag = Spec.from_literal({"mpileaks": {"callpath": {"libelf": {"libdwarf": None}}}})

        # All these are equal to each other with regular ==
        specs = (flat, flat_init, flip_flat, dag, flip_dag)
        for lhs, rhs in zip(specs, specs):
            assert lhs == rhs
            assert str(lhs) == str(rhs)

        # Same DAGs constructed different ways are equal
        assert flat.eq_dag(flat_init)

        # order at same level does not matter -- (dep on same parent)
        assert flat.eq_dag(flip_flat)

        # DAGs should be unequal if nesting is different
        assert not flat.eq_dag(dag)
        assert not flat.eq_dag(flip_dag)
        assert not flip_flat.eq_dag(dag)
        assert not flip_flat.eq_dag(flip_dag)
        assert not dag.eq_dag(flip_dag)

    def test_normalize_mpileaks(self):
        # Spec parsed in from a string
        spec = Spec.from_literal(
            {"mpileaks ^mpich ^callpath ^dyninst ^libelf@1.8.11 ^libdwarf": None}
        )

        # What that spec should look like after parsing
        expected_flat = Spec.from_literal(
            {
                "mpileaks": {
                    "mpich": None,
                    "callpath": None,
                    "dyninst": None,
                    "libelf@1.8.11": None,
                    "libdwarf": None,
                }
            }
        )

        # What it should look like after normalization
        mpich = Spec("mpich")
        libelf = Spec("libelf@1.8.11")
        expected_normalized = Spec.from_literal(
            {
                "mpileaks": {
                    "callpath": {
                        "dyninst": {"libdwarf": {libelf: None}, libelf: None},
                        mpich: None,
                    },
                    mpich: None,
                }
            }
        )

        # Similar to normalized spec, but now with copies of the same
        # libelf node.  Normalization should result in a single unique
        # node for each package, so this is the wrong DAG.
        non_unique_nodes = Spec.from_literal(
            {
                "mpileaks": {
                    "callpath": {
                        "dyninst": {"libdwarf": {"libelf@1.8.11": None}, "libelf@1.8.11": None},
                        mpich: None,
                    },
                    mpich: None,
                }
            },
            normal=False,
        )

        # All specs here should be equal under regular equality
        specs = (spec, expected_flat, expected_normalized, non_unique_nodes)
        for lhs, rhs in zip(specs, specs):
            assert lhs == rhs
            assert str(lhs) == str(rhs)

        # Test that equal and equal_dag are doing the right thing
        assert spec == expected_flat
        assert spec.eq_dag(expected_flat)

        # Normalized has different DAG structure, so NOT equal.
        assert spec != expected_normalized
        assert not spec.eq_dag(expected_normalized)

        # Again, different DAG structure so not equal.
        assert spec != non_unique_nodes
        assert not spec.eq_dag(non_unique_nodes)

        spec.normalize()

        # After normalizing, spec_dag_equal should match the normalized spec.
        assert spec != expected_flat
        assert not spec.eq_dag(expected_flat)

        # verify DAG structure without deptypes.
        assert spec.eq_dag(expected_normalized, deptypes=False)
        assert not spec.eq_dag(non_unique_nodes, deptypes=False)

        assert not spec.eq_dag(expected_normalized, deptypes=True)
        assert not spec.eq_dag(non_unique_nodes, deptypes=True)

    @pytest.mark.xfail(reason="String representation changed")
    def test_normalize_with_virtual_package(self):
        spec = Spec("mpileaks ^mpi ^libelf@1.8.11 ^libdwarf")
        spec.normalize()

        expected_normalized = Spec.from_literal(
            {
                "mpileaks": {
                    "callpath": {
                        "dyninst": {"libdwarf": {"libelf@1.8.11": None}, "libelf@1.8.11": None},
                        "mpi": None,
                    },
                    "mpi": None,
                }
            }
        )

        assert str(spec) == str(expected_normalized)

    def test_contains(self):
        spec = Spec("mpileaks ^mpi ^libelf@1.8.11 ^libdwarf")
        assert Spec("mpi") in spec
        assert Spec("libelf") in spec
        assert Spec("libelf@1.8.11") in spec
        assert Spec("libelf@1.8.12") not in spec
        assert Spec("libdwarf") in spec
        assert Spec("libgoblin") not in spec
        assert Spec("mpileaks") in spec

    def test_copy_simple(self):
        orig = Spec("mpileaks")
        copy = orig.copy()
        check_links(copy)

        assert orig == copy
        assert orig.eq_dag(copy)
        assert orig._normal == copy._normal
        assert orig._concrete == copy._concrete

        # ensure no shared nodes bt/w orig and copy.
        orig_ids = set(id(s) for s in orig.traverse())
        copy_ids = set(id(s) for s in copy.traverse())
        assert not orig_ids.intersection(copy_ids)

    def test_copy_normalized(self):
        orig = Spec("mpileaks")
        orig.normalize()
        copy = orig.copy()
        check_links(copy)

        assert orig == copy
        assert orig.eq_dag(copy)

        # ensure no shared nodes bt/w orig and copy.
        orig_ids = set(id(s) for s in orig.traverse())
        copy_ids = set(id(s) for s in copy.traverse())
        assert not orig_ids.intersection(copy_ids)

    def test_copy_concretized(self):
        orig = Spec("mpileaks")
        orig.concretize()
        copy = orig.copy()

        check_links(copy)

        assert orig == copy
        assert orig.eq_dag(copy)
        assert orig._normal == copy._normal
        assert orig._concrete == copy._concrete

        # ensure no shared nodes bt/w orig and copy.
        orig_ids = set(id(s) for s in orig.traverse())
        copy_ids = set(id(s) for s in copy.traverse())
        assert not orig_ids.intersection(copy_ids)

    def test_copy_through_spec_build_interface(self):
        """Check that copying dependencies using id(node) as a fast identifier of the
        node works when the spec is wrapped in a SpecBuildInterface object.
        """
        s = Spec("mpileaks").concretized()

        c0 = s.copy()
        assert c0 == s

        # Single indirection
        c1 = s["mpileaks"].copy()
        assert c0 == c1 == s

        # Double indirection
        c2 = s["mpileaks"]["mpileaks"].copy()
        assert c0 == c1 == c2 == s

    """
    Here is the graph with deptypes labeled (assume all packages have a 'dt'
    prefix). Arrows are marked with the deptypes ('b' for 'build', 'l' for
    'link', 'r' for 'run').

        use -bl-> top

        top -b->  build1
        top -bl-> link1
        top -r->  run1

        build1 -b->  build2
        build1 -bl-> link2
        build1 -r->  run2

        link1 -bl-> link3

        run1 -bl-> link5
        run1 -r->  run3

        link3 -b->  build2
        link3 -bl-> link4

        run3 -b-> build3
    """

    def test_deptype_traversal(self):
        dag = Spec("dtuse")
        dag.normalize()

        names = [
            "dtuse",
            "dttop",
            "dtbuild1",
            "dtbuild2",
            "dtlink2",
            "dtlink1",
            "dtlink3",
            "dtlink4",
        ]

        traversal = dag.traverse(deptype=("build", "link"))
        assert [x.name for x in traversal] == names

    def test_deptype_traversal_with_builddeps(self):
        dag = Spec("dttop")
        dag.normalize()

        names = ["dttop", "dtbuild1", "dtbuild2", "dtlink2", "dtlink1", "dtlink3", "dtlink4"]

        traversal = dag.traverse(deptype=("build", "link"))
        assert [x.name for x in traversal] == names

    def test_deptype_traversal_full(self):
        dag = Spec("dttop")
        dag.normalize()

        names = [
            "dttop",
            "dtbuild1",
            "dtbuild2",
            "dtlink2",
            "dtrun2",
            "dtlink1",
            "dtlink3",
            "dtlink4",
            "dtrun1",
            "dtlink5",
            "dtrun3",
            "dtbuild3",
        ]

        traversal = dag.traverse(deptype=all)
        assert [x.name for x in traversal] == names

    def test_deptype_traversal_run(self):
        dag = Spec("dttop")
        dag.normalize()

        names = ["dttop", "dtrun1", "dtrun3"]

        traversal = dag.traverse(deptype="run")
        assert [x.name for x in traversal] == names

    def test_hash_bits(self):
        """Ensure getting first n bits of a base32-encoded DAG hash works."""

        # RFC 4648 base32 decode table
        b32 = dict((j, i) for i, j in enumerate("abcdefghijklmnopqrstuvwxyz"))
        b32.update(dict((j, i) for i, j in enumerate("234567", 26)))

        # some package hashes
        tests = [
            "35orsd4cenv743hg4i5vxha2lzayycby",
            "6kfqtj7dap3773rxog6kkmoweix5gpwo",
            "e6h6ff3uvmjbq3azik2ckr6ckwm3depv",
            "snz2juf4ij7sv77cq3vs467q6acftmur",
            "4eg47oedi5bbkhpoxw26v3oe6vamkfd7",
            "vrwabwj6umeb5vjw6flx2rnft3j457rw",
        ]

        for test_hash in tests:
            # string containing raw bits of hash ('1' and '0')
            expected = "".join([format(b32[c], "#07b").replace("0b", "") for c in test_hash])

            for bits in (1, 2, 3, 4, 7, 8, 9, 16, 64, 117, 128, 160):
                actual_int = hashutil.base32_prefix_bits(test_hash, bits)
                fmt = "#0%sb" % (bits + 2)
                actual = format(actual_int, fmt).replace("0b", "")

                assert expected[:bits] == actual

            with pytest.raises(ValueError):
                hashutil.base32_prefix_bits(test_hash, 161)

            with pytest.raises(ValueError):
                hashutil.base32_prefix_bits(test_hash, 256)

    def test_traversal_directions(self):
        """Make sure child and parent traversals of specs work."""
        # Mock spec - d is used for a diamond dependency
        spec = Spec.from_literal(
            {"a": {"b": {"c": {"d": None}, "e": None}, "f": {"g": {"d": None}}}}
        )

        assert ["a", "b", "c", "d", "e", "f", "g"] == [
            s.name for s in spec.traverse(direction="children")
        ]

        assert ["g", "f", "a"] == [s.name for s in spec["g"].traverse(direction="parents")]

        assert ["d", "c", "b", "a", "g", "f"] == [
            s.name for s in spec["d"].traverse(direction="parents")
        ]

    def test_edge_traversals(self):
        """Make sure child and parent traversals of specs work."""
        # Mock spec - d is used for a diamond dependency
        spec = Spec.from_literal(
            {"a": {"b": {"c": {"d": None}, "e": None}, "f": {"g": {"d": None}}}}
        )

        assert ["a", "b", "c", "d", "e", "f", "g"] == [
            s.name for s in spec.traverse(direction="children")
        ]

        assert ["g", "f", "a"] == [s.name for s in spec["g"].traverse(direction="parents")]

        assert ["d", "c", "b", "a", "g", "f"] == [
            s.name for s in spec["d"].traverse(direction="parents")
        ]

    def test_copy_dependencies(self):
        s1 = Spec("mpileaks ^mpich2@1.1")
        s2 = s1.copy()

        assert "^mpich2@1.1" in s2
        assert "^mpich2" in s2

    def test_construct_spec_with_deptypes(self):
        """Ensure that it is possible to construct a spec with explicit
        dependency types."""
        s = Spec.from_literal(
            {"a": {"b": {"c:build": None}, "d": {"e:build,link": {"f:run": None}}}}
        )

        assert s["b"].edges_to_dependencies(name="c")[0].depflag == dt.BUILD
        assert s["d"].edges_to_dependencies(name="e")[0].depflag == dt.BUILD | dt.LINK
        assert s["e"].edges_to_dependencies(name="f")[0].depflag == dt.RUN

        assert s["c"].edges_from_dependents(name="b")[0].depflag == dt.BUILD
        assert s["e"].edges_from_dependents(name="d")[0].depflag == dt.BUILD | dt.LINK
        assert s["f"].edges_from_dependents(name="e")[0].depflag == dt.RUN

    def check_diamond_deptypes(self, spec):
        """Validate deptypes in dt-diamond spec.

        This ensures that concretization works properly when two packages
        depend on the same dependency in different ways.

        """
        assert (
            spec["dt-diamond"].edges_to_dependencies(name="dt-diamond-left")[0].depflag
            == dt.BUILD | dt.LINK
        )
        assert (
            spec["dt-diamond"].edges_to_dependencies(name="dt-diamond-right")[0].depflag
            == dt.BUILD | dt.LINK
        )
        assert (
            spec["dt-diamond-left"].edges_to_dependencies(name="dt-diamond-bottom")[0].depflag
            == dt.BUILD
        )
        assert (
            spec["dt-diamond-right"].edges_to_dependencies(name="dt-diamond-bottom")[0].depflag
            == dt.BUILD | dt.LINK | dt.RUN
        )

    def check_diamond_normalized_dag(self, spec):
        dag = Spec.from_literal(
            {
                "dt-diamond": {
                    "dt-diamond-left:build,link": {"dt-diamond-bottom:build": None},
                    "dt-diamond-right:build,link": {"dt-diamond-bottom:build,link,run": None},
                }
            }
        )

        assert spec.eq_dag(dag)

    def test_normalize_diamond_deptypes(self):
        """Ensure that dependency types are preserved even if the same thing is
        depended on in two different ways."""
        s = Spec("dt-diamond")
        s.normalize()

        self.check_diamond_deptypes(s)
        self.check_diamond_normalized_dag(s)

    def test_concretize_deptypes(self):
        """Ensure that dependency types are preserved after concretization."""
        s = Spec("dt-diamond")
        s.concretize()
        self.check_diamond_deptypes(s)

    def test_copy_deptypes(self):
        """Ensure that dependency types are preserved by spec copy."""
        s1 = Spec("dt-diamond")
        s1.normalize()
        self.check_diamond_deptypes(s1)
        self.check_diamond_normalized_dag(s1)

        s2 = s1.copy()
        self.check_diamond_normalized_dag(s2)
        self.check_diamond_deptypes(s2)

        s3 = Spec("dt-diamond")
        s3.concretize()
        self.check_diamond_deptypes(s3)

        s4 = s3.copy()
        self.check_diamond_deptypes(s4)

    def test_getitem_query(self):
        s = Spec("mpileaks")
        s.concretize()

        # Check a query to a non-virtual package
        a = s["callpath"]

        query = a.last_query
        assert query.name == "callpath"
        assert len(query.extra_parameters) == 0
        assert not query.isvirtual

        # Check a query to a virtual package
        a = s["mpi"]

        query = a.last_query
        assert query.name == "mpi"
        assert len(query.extra_parameters) == 0
        assert query.isvirtual

        # Check a query to a virtual package with
        # extra parameters after query
        a = s["mpi:cxx,fortran"]

        query = a.last_query
        assert query.name == "mpi"
        assert len(query.extra_parameters) == 2
        assert "cxx" in query.extra_parameters
        assert "fortran" in query.extra_parameters
        assert query.isvirtual

    def test_getitem_exceptional_paths(self):
        s = Spec("mpileaks")
        s.concretize()
        # Needed to get a proxy object
        q = s["mpileaks"]

        # Test that the attribute is read-only
        with pytest.raises(AttributeError):
            q.libs = "foo"

        with pytest.raises(AttributeError):
            q.libs

    def test_canonical_deptype(self):
        # special values
        assert dt.canonicalize(all) == dt.ALL
        assert dt.canonicalize("all") == dt.ALL

        with pytest.raises(ValueError):
            dt.canonicalize(None)
        with pytest.raises(ValueError):
            dt.canonicalize([None])

        # everything in all_types is canonical
        for v in dt.ALL_TYPES:
            assert dt.canonicalize(v) == dt.flag_from_string(v)

        # tuples
        assert dt.canonicalize(("build",)) == dt.BUILD
        assert dt.canonicalize(("build", "link", "run")) == dt.BUILD | dt.LINK | dt.RUN
        assert dt.canonicalize(("build", "link")) == dt.BUILD | dt.LINK
        assert dt.canonicalize(("build", "run")) == dt.BUILD | dt.RUN

        # lists
        assert dt.canonicalize(["build", "link", "run"]) == dt.BUILD | dt.LINK | dt.RUN
        assert dt.canonicalize(["build", "link"]) == dt.BUILD | dt.LINK
        assert dt.canonicalize(["build", "run"]) == dt.BUILD | dt.RUN

        # sorting
        assert dt.canonicalize(("run", "build", "link")) == dt.BUILD | dt.LINK | dt.RUN
        assert dt.canonicalize(("run", "link", "build")) == dt.BUILD | dt.LINK | dt.RUN
        assert dt.canonicalize(("run", "link")) == dt.LINK | dt.RUN
        assert dt.canonicalize(("link", "build")) == dt.BUILD | dt.LINK

        # deduplication
        assert dt.canonicalize(("run", "run", "link")) == dt.RUN | dt.LINK
        assert dt.canonicalize(("run", "link", "link")) == dt.RUN | dt.LINK

        # can't put 'all' in tuple or list
        with pytest.raises(ValueError):
            dt.canonicalize(["all"])
        with pytest.raises(ValueError):
            dt.canonicalize(("all",))

        # invalid values
        with pytest.raises(ValueError):
            dt.canonicalize("foo")
        with pytest.raises(ValueError):
            dt.canonicalize(("foo", "bar"))
        with pytest.raises(ValueError):
            dt.canonicalize(("foo",))

    def test_invalid_literal_spec(self):
        # Can't give type 'build' to a top-level spec
        with pytest.raises(spack.parser.SpecSyntaxError):
            Spec.from_literal({"foo:build": None})

        # Can't use more than one ':' separator
        with pytest.raises(KeyError):
            Spec.from_literal({"foo": {"bar:build:link": None}})

    def test_spec_tree_respect_deptypes(self):
        # Version-test-root uses version-test-pkg as a build dependency
        s = Spec("version-test-root").concretized()
        out = s.tree(deptypes="all")
        assert "version-test-pkg" in out
        out = s.tree(deptypes=("link", "run"))
        assert "version-test-pkg" not in out


def test_synthetic_construction_of_split_dependencies_from_same_package(mock_packages, config):
    # Construct in a synthetic way (i.e. without using the solver)
    # the following spec:
    #
    #          b
    #  build /   \ link,run
    #    c@2.0   c@1.0
    #
    # To demonstrate that a spec can now hold two direct
    # dependencies from the same package
    root = Spec("b").concretized()
    link_run_spec = Spec("c@=1.0").concretized()
    build_spec = Spec("c@=2.0").concretized()

    root.add_dependency_edge(link_run_spec, depflag=dt.LINK, virtuals=())
    root.add_dependency_edge(link_run_spec, depflag=dt.RUN, virtuals=())
    root.add_dependency_edge(build_spec, depflag=dt.BUILD, virtuals=())

    # Check dependencies from the perspective of root
    assert len(root.dependencies()) == 2
    assert all(x.name == "c" for x in root.dependencies())

    assert "@2.0" in root.dependencies(name="c", deptype=dt.BUILD)[0]
    assert "@1.0" in root.dependencies(name="c", deptype=dt.LINK | dt.RUN)[0]

    # Check parent from the perspective of the dependencies
    assert len(build_spec.dependents()) == 1
    assert len(link_run_spec.dependents()) == 1
    assert build_spec.dependents() == link_run_spec.dependents()
    assert build_spec != link_run_spec


def test_synthetic_construction_bootstrapping(mock_packages, config):
    # Construct the following spec:
    #
    #  b@2.0
    #    | build
    #  b@1.0
    #
    root = Spec("b@=2.0").concretized()
    bootstrap = Spec("b@=1.0").concretized()

    root.add_dependency_edge(bootstrap, depflag=dt.BUILD, virtuals=())

    assert len(root.dependencies()) == 1
    assert root.dependencies()[0].name == "b"
    assert root.name == "b"


def test_addition_of_different_deptypes_in_multiple_calls(mock_packages, config):
    # Construct the following spec:
    #
    #  b@2.0
    #    | build,link,run
    #  b@1.0
    #
    # with three calls and check we always have a single edge
    root = Spec("b@=2.0").concretized()
    bootstrap = Spec("b@=1.0").concretized()

    for current_depflag in (dt.BUILD, dt.LINK, dt.RUN):
        root.add_dependency_edge(bootstrap, depflag=current_depflag, virtuals=())

        # Check edges in dependencies
        assert len(root.edges_to_dependencies()) == 1
        forward_edge = root.edges_to_dependencies(depflag=current_depflag)[0]
        assert current_depflag & forward_edge.depflag
        assert id(forward_edge.parent) == id(root)
        assert id(forward_edge.spec) == id(bootstrap)

        # Check edges from dependents
        assert len(bootstrap.edges_from_dependents()) == 1
        backward_edge = bootstrap.edges_from_dependents(depflag=current_depflag)[0]
        assert current_depflag & backward_edge.depflag
        assert id(backward_edge.parent) == id(root)
        assert id(backward_edge.spec) == id(bootstrap)


@pytest.mark.parametrize(
    "c1_depflag,c2_depflag",
    [(dt.LINK, dt.BUILD | dt.LINK), (dt.LINK | dt.RUN, dt.BUILD | dt.LINK)],
)
def test_adding_same_deptype_with_the_same_name_raises(
    mock_packages, config, c1_depflag, c2_depflag
):
    p = Spec("b@=2.0").concretized()
    c1 = Spec("b@=1.0").concretized()
    c2 = Spec("b@=2.0").concretized()

    p.add_dependency_edge(c1, depflag=c1_depflag, virtuals=())
    with pytest.raises(spack.error.SpackError):
        p.add_dependency_edge(c2, depflag=c2_depflag, virtuals=())


@pytest.mark.regression("33499")
def test_indexing_prefers_direct_or_transitive_link_deps():
    # Test whether spec indexing prefers direct/transitive link type deps over deps of
    # build/run/test deps, and whether it does fall back to a full dag search.
    root = Spec("root")

    # Use a and z to since we typically traverse by edges sorted alphabetically.
    a1 = Spec("a1")
    a2 = Spec("a2")
    z1 = Spec("z1")
    z2 = Spec("z2")

    # Same package, different spec.
    z3_flavor_1 = Spec("z3 +through_a1")
    z3_flavor_2 = Spec("z3 +through_z1")

    root.add_dependency_edge(a1, depflag=dt.BUILD | dt.RUN | dt.TEST, virtuals=())

    # unique package as a dep of a build/run/test type dep.
    a1.add_dependency_edge(a2, depflag=dt.ALL, virtuals=())
    a1.add_dependency_edge(z3_flavor_1, depflag=dt.ALL, virtuals=())

    # chain of link type deps root -> z1 -> z2 -> z3
    root.add_dependency_edge(z1, depflag=dt.LINK, virtuals=())
    z1.add_dependency_edge(z2, depflag=dt.LINK, virtuals=())
    z2.add_dependency_edge(z3_flavor_2, depflag=dt.LINK, virtuals=())

    # Indexing should prefer the link-type dep.
    assert "through_z1" in root["z3"].variants
    assert "through_a1" in a1["z3"].variants

    # Ensure that the full DAG is still searched
    assert root["a2"]