summaryrefslogtreecommitdiff
path: root/lib/spack/spack/util/mock_package.py
blob: ab7b8439e590678673a602750a3707a116d6fab8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# Copyright 2013-2021 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)
"""Infrastructure used by tests for mocking packages and repos."""
import collections

import spack.provider_index
import spack.util.naming
from spack.dependency import Dependency
from spack.spec import Spec
from spack.version import Version

__all__ = ["MockPackageMultiRepo"]


class MockPackageBase(object):
    """Internal base class for mocking ``spack.package.PackageBase``.

    Use ``MockPackageMultiRepo.add_package()`` to create new instances.

    """
    virtual = False

    def __init__(self, dependencies, dependency_types,
                 conditions=None, versions=None):
        """Instantiate a new MockPackageBase.

        This is not for general use; it needs to be constructed by a
        ``MockPackageMultiRepo``, as we need to know about *all* packages
        to find possible depenencies.

        """
        self.spec = None
        self._installed_upstream = False

    def provides(self, vname):
        return vname in self.provided

    @property
    def virtuals_provided(self):
        return [v.name for v, c in self.provided]

    @classmethod
    def possible_dependencies(
            cls, transitive=True, deptype='all', visited=None, virtuals=None):
        visited = {} if visited is None else visited

        for name, conditions in cls.dependencies.items():
            # check whether this dependency could be of the type asked for
            types = [dep.type for cond, dep in conditions.items()]
            types = set.union(*types)
            if not any(d in types for d in deptype):
                continue

        visited.setdefault(cls.name, set())
        for dep_name in cls.dependencies:
            if dep_name in visited:
                continue

            visited.setdefault(dep_name, set())

            if not transitive:
                continue

            cls._repo.get(dep_name).possible_dependencies(
                transitive, deptype, visited, virtuals)

        return visited

    def content_hash(self):
        # Unlike real packages, MockPackage doesn't have a corresponding
        # package.py file; in that sense, the content_hash is always the same.
        return self.__class__.__name__


class MockPackageMultiRepo(object):
    """Mock package repository, mimicking ``spack.repo.Repo``."""

    def __init__(self):
        self.spec_to_pkg = {}
        self.namespace = 'mock'                 # repo namespace
        self.full_namespace = 'spack.pkg.mock'  # python import namespace

    def get(self, spec):
        if not isinstance(spec, spack.spec.Spec):
            spec = Spec(spec)
        if spec.name not in self.spec_to_pkg:
            raise spack.repo.UnknownPackageError(spec.fullname)
        return self.spec_to_pkg[spec.name]

    def get_pkg_class(self, name):
        namespace, _, name = name.rpartition(".")
        if namespace and namespace != self.namespace:
            raise spack.repo.InvalidNamespaceError(
                "bad namespace: %s" % self.namespace)
        return self.spec_to_pkg[name]

    def exists(self, name):
        return name in self.spec_to_pkg

    def is_virtual(self, name, use_index=True):
        return False

    def repo_for_pkg(self, name):
        import collections
        Repo = collections.namedtuple('Repo', ['namespace'])
        return Repo('mockrepo')

    def __contains__(self, item):
        return item in self.spec_to_pkg

    def add_package(self, name, dependencies=None, dependency_types=None,
                    conditions=None):
        """Factory method for creating mock packages.

        This creates a new subclass of ``MockPackageBase``, ensures that its
        ``name`` and ``__name__`` properties are set up correctly, and
        returns a new instance.

        We use a factory function here because many functions and properties
        of packages need to be class functions.

        Args:
            name (str): name of the new package
            dependencies (list): list of mock packages to be dependencies
                for this new package (optional; no deps if not provided)
            dependency_type (list): list of deptypes for each dependency
                (optional; will be default_deptype if not provided)
            conditions (list): condition specs for each dependency (optional)

        """
        if not dependencies:
            dependencies = []

        if not dependency_types:
            dependency_types = [
                spack.dependency.default_deptype] * len(dependencies)

        assert len(dependencies) == len(dependency_types)

        # new class for the mock package
        class MockPackage(MockPackageBase):
            pass
        MockPackage.__name__ = spack.util.naming.mod_to_class(name)
        MockPackage.name = name
        MockPackage._repo = self

        # set up dependencies
        MockPackage.dependencies = collections.OrderedDict()
        for dep, dtype in zip(dependencies, dependency_types):
            d = Dependency(MockPackage, Spec(dep.name), type=dtype)
            if not conditions or dep.name not in conditions:
                MockPackage.dependencies[dep.name] = {Spec(name): d}
            else:
                dep_conditions = conditions[dep.name]
                dep_conditions = dict(
                    (Spec(x), Dependency(MockPackage, Spec(y), type=dtype))
                    for x, y in dep_conditions.items())
                MockPackage.dependencies[dep.name] = dep_conditions

        # each package has some fake versions
        versions = list(Version(x) for x in [1, 2, 3])
        MockPackage.versions = dict(
            (x, {'preferred': False}) for x in versions
        )

        MockPackage.variants = {}
        MockPackage.provided = {}
        MockPackage.conflicts = {}
        MockPackage.patches = {}

        mock_package = MockPackage(
            dependencies, dependency_types, conditions, versions)
        self.spec_to_pkg[name] = mock_package
        self.spec_to_pkg["mockrepo." + name] = mock_package

        return mock_package

    @property
    def provider_index(self):
        return spack.provider_index.ProviderIndex()