tests: each mock package now has its own class (#16157)

Packages in Spack are classes, and we need to be able to execute class
methods on mock packages.  The previous design used instances of a single
MockPackage class; this version gives each package its own class that can
spider depenencies.  This allows us to implement class methods like
`possible_dependencies()` on mock packages.

This design change moves mock package creation into the
`MockPackageMultiRepo`, and mock packages now *must* be created from a
repo.  This is required for us to mock `possible_dependencies()`, which
needs to be able to get dependency packages from the package repo.

Changes include:

* `MockPackage` is now `MockPackageBase`
* `MockPackageBase` instances must now be created with
  `MockPackageMultiRepo.add_package()`
* add `possible_dependencies()` method to `MockPackageBase`
* refactor tests to use new code structure
* move package mocking infrastructure into `spack.util.mock_package`,
  as it's becoming a more sophisticated class and it gets lots in `conftest.py`
This commit is contained in:
Todd Gamblin 2020-04-23 18:21:49 -07:00 committed by GitHub
parent 433a0b243f
commit c6ada206af
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 279 additions and 153 deletions

View file

@ -18,7 +18,7 @@
import spack.paths as spack_paths
import spack.repo as repo
from spack.spec import Spec
from spack.test.conftest import MockPackage, MockPackageMultiRepo
from spack.util.mock_package import MockPackageMultiRepo
import spack.util.executable as exe
import spack.util.spack_yaml as syaml
import spack.util.gpg
@ -101,15 +101,14 @@ def test_specs_staging(config):
"""
default = ('build', 'link')
g = MockPackage('g', [], [])
f = MockPackage('f', [], [])
e = MockPackage('e', [], [])
d = MockPackage('d', [f, g], [default, default])
c = MockPackage('c', [], [])
b = MockPackage('b', [d, e], [default, default])
a = MockPackage('a', [b, c], [default, default])
mock_repo = MockPackageMultiRepo([a, b, c, d, e, f, g])
mock_repo = MockPackageMultiRepo()
g = mock_repo.add_package('g', [], [])
f = mock_repo.add_package('f', [], [])
e = mock_repo.add_package('e', [], [])
d = mock_repo.add_package('d', [f, g], [default, default])
c = mock_repo.add_package('c', [], [])
b = mock_repo.add_package('b', [d, e], [default, default])
mock_repo.add_package('a', [b, c], [default, default])
with repo.swap(mock_repo):
spec_a = Spec('a')

View file

@ -20,7 +20,7 @@
from spack.stage import stage_prefix
from spack.spec_list import SpecListError
from spack.test.conftest import MockPackage, MockPackageMultiRepo
from spack.util.mock_package import MockPackageMultiRepo
import spack.util.spack_json as sjson
@ -733,10 +733,10 @@ def create_v1_lockfile_dict(roots, all_specs):
def test_read_old_lock_and_write_new(tmpdir):
build_only = ('build',)
y = MockPackage('y', [], [])
x = MockPackage('x', [y], [build_only])
mock_repo = MockPackageMultiRepo()
y = mock_repo.add_package('y', [], [])
mock_repo.add_package('x', [y], [build_only])
mock_repo = MockPackageMultiRepo([x, y])
with spack.repo.swap(mock_repo):
x = Spec('x')
x.concretize()
@ -765,9 +765,9 @@ def test_read_old_lock_creates_backup(tmpdir):
"""When reading a version-1 lockfile, make sure that a backup of that file
is created.
"""
y = MockPackage('y', [], [])
mock_repo = MockPackageMultiRepo()
y = mock_repo.add_package('y', [], [])
mock_repo = MockPackageMultiRepo([y])
with spack.repo.swap(mock_repo):
y = Spec('y')
y.concretize()
@ -796,11 +796,10 @@ def test_indirect_build_dep():
default = ('build', 'link')
build_only = ('build',)
z = MockPackage('z', [], [])
y = MockPackage('y', [z], [build_only])
x = MockPackage('x', [y], [default])
mock_repo = MockPackageMultiRepo([x, y, z])
mock_repo = MockPackageMultiRepo()
z = mock_repo.add_package('z', [], [])
y = mock_repo.add_package('y', [z], [build_only])
mock_repo.add_package('x', [y], [default])
def noop(*args):
pass
@ -838,11 +837,10 @@ def test_store_different_build_deps():
default = ('build', 'link')
build_only = ('build',)
z = MockPackage('z', [], [])
y = MockPackage('y', [z], [build_only])
x = MockPackage('x', [y, z], [default, build_only])
mock_repo = MockPackageMultiRepo([x, y, z])
mock_repo = MockPackageMultiRepo()
z = mock_repo.add_package('z', [], [])
y = mock_repo.add_package('y', [z], [build_only])
mock_repo.add_package('x', [y, z], [default, build_only])
def noop(*args):
pass

View file

@ -15,7 +15,7 @@
from spack.package_prefs import PackagePrefs
from spack.spec import Spec, CompilerSpec, ConflictsInSpecError
from spack.version import ver
from spack.test.conftest import MockPackage, MockPackageMultiRepo
from spack.util.mock_package import MockPackageMultiRepo
import spack.compilers
import spack.platforms.test
@ -235,10 +235,10 @@ def test_architecture_deep_inheritance(self):
"""
default_dep = ('link', 'build')
bazpkg = MockPackage('bazpkg', [], [])
barpkg = MockPackage('barpkg', [bazpkg], [default_dep])
foopkg = MockPackage('foopkg', [barpkg], [default_dep])
mock_repo = MockPackageMultiRepo([foopkg, barpkg, bazpkg])
mock_repo = MockPackageMultiRepo()
bazpkg = mock_repo.add_package('bazpkg', [], [])
barpkg = mock_repo.add_package('barpkg', [bazpkg], [default_dep])
mock_repo.add_package('foopkg', [barpkg], [default_dep])
with spack.repo.swap(mock_repo):
spec = Spec('foopkg %clang@3.3 os=CNL target=footar' +

View file

@ -14,7 +14,6 @@
import tempfile
import xml.etree.ElementTree
import ordereddict_backport
import py
import pytest
import ruamel.yaml as yaml
@ -38,11 +37,8 @@
import spack.util.gpg
from spack.util.pattern import Bunch
from spack.dependency import Dependency
from spack.fetch_strategy import FetchStrategyComposite, URLFetchStrategy
from spack.fetch_strategy import FetchError
from spack.spec import Spec
from spack.version import Version
@pytest.fixture
@ -1004,75 +1000,6 @@ def installation_dir_with_headers(tmpdir_factory):
return root
##########
# Mock packages
##########
class MockPackage(object):
def __init__(self, name, dependencies, dependency_types, conditions=None,
versions=None):
self.name = name
self.spec = None
self.dependencies = ordereddict_backport.OrderedDict()
self._installed_upstream = False
assert len(dependencies) == len(dependency_types)
for dep, dtype in zip(dependencies, dependency_types):
d = Dependency(self, Spec(dep.name), type=dtype)
if not conditions or dep.name not in conditions:
self.dependencies[dep.name] = {Spec(name): d}
else:
dep_conditions = conditions[dep.name]
dep_conditions = dict(
(Spec(x), Dependency(self, Spec(y), type=dtype))
for x, y in dep_conditions.items())
self.dependencies[dep.name] = dep_conditions
if versions:
self.versions = versions
else:
versions = list(Version(x) for x in [1, 2, 3])
self.versions = dict((x, {'preferred': False}) for x in versions)
self.variants = {}
self.provided = {}
self.conflicts = {}
self.patches = {}
def provides(self, vname):
return vname in self.provided
@property
def virtuals_provided(self):
return [v.name for v, c in self.provided]
class MockPackageMultiRepo(object):
def __init__(self, packages):
self.spec_to_pkg = dict((x.name, x) for x in packages)
self.spec_to_pkg.update(
dict(('mockrepo.' + x.name, x) for x in packages))
def get(self, spec):
if not isinstance(spec, spack.spec.Spec):
spec = Spec(spec)
return self.spec_to_pkg[spec.name]
def get_pkg_class(self, name):
return self.spec_to_pkg[name]
def exists(self, name):
return name in self.spec_to_pkg
def is_virtual(self, name):
return False
def repo_for_pkg(self, name):
import collections
Repo = collections.namedtuple('Repo', ['namespace'])
return Repo('mockrepo')
##########
# Specs of various kind
##########

View file

@ -28,7 +28,7 @@
import spack.database
import spack.package
import spack.spec
from spack.test.conftest import MockPackage, MockPackageMultiRepo
from spack.util.mock_package import MockPackageMultiRepo
from spack.util.executable import Executable
@ -73,11 +73,11 @@ def test_installed_upstream(upstream_and_downstream_db):
downstream_db, downstream_layout = (upstream_and_downstream_db)
default = ('build', 'link')
x = MockPackage('x', [], [])
z = MockPackage('z', [], [])
y = MockPackage('y', [z], [default])
w = MockPackage('w', [x, y], [default, default])
mock_repo = MockPackageMultiRepo([w, x, y, z])
mock_repo = MockPackageMultiRepo()
x = mock_repo.add_package('x', [], [])
z = mock_repo.add_package('z', [], [])
y = mock_repo.add_package('y', [z], [default])
mock_repo.add_package('w', [x, y], [default, default])
with spack.repo.swap(mock_repo):
spec = spack.spec.Spec('w')
@ -116,9 +116,9 @@ def test_removed_upstream_dep(upstream_and_downstream_db):
downstream_db, downstream_layout = (upstream_and_downstream_db)
default = ('build', 'link')
z = MockPackage('z', [], [])
y = MockPackage('y', [z], [default])
mock_repo = MockPackageMultiRepo([y, z])
mock_repo = MockPackageMultiRepo()
z = mock_repo.add_package('z', [], [])
mock_repo.add_package('y', [z], [default])
with spack.repo.swap(mock_repo):
spec = spack.spec.Spec('y')
@ -150,8 +150,8 @@ def test_add_to_upstream_after_downstream(upstream_and_downstream_db):
upstream_write_db, upstream_db, upstream_layout,\
downstream_db, downstream_layout = (upstream_and_downstream_db)
x = MockPackage('x', [], [])
mock_repo = MockPackageMultiRepo([x])
mock_repo = MockPackageMultiRepo()
mock_repo.add_package('x', [], [])
with spack.repo.swap(mock_repo):
spec = spack.spec.Spec('x')
@ -183,8 +183,8 @@ def test_cannot_write_upstream(tmpdir_factory, test_store, gen_mock_layout):
roots = [str(tmpdir_factory.mktemp(x)) for x in ['a', 'b']]
layouts = [gen_mock_layout(x) for x in ['/ra/', '/rb/']]
x = MockPackage('x', [], [])
mock_repo = MockPackageMultiRepo([x])
mock_repo = MockPackageMultiRepo()
mock_repo.add_package('x', [], [])
# Instantiate the database that will be used as the upstream DB and make
# sure it has an index file
@ -209,11 +209,10 @@ def test_recursive_upstream_dbs(tmpdir_factory, test_store, gen_mock_layout):
layouts = [gen_mock_layout(x) for x in ['/ra/', '/rb/', '/rc/']]
default = ('build', 'link')
z = MockPackage('z', [], [])
y = MockPackage('y', [z], [default])
x = MockPackage('x', [y], [default])
mock_repo = MockPackageMultiRepo([x, y, z])
mock_repo = MockPackageMultiRepo()
z = mock_repo.add_package('z', [], [])
y = mock_repo.add_package('y', [z], [default])
mock_repo.add_package('x', [y], [default])
with spack.repo.swap(mock_repo):
spec = spack.spec.Spec('x')
@ -675,7 +674,7 @@ def test_115_reindex_with_packages_not_in_repo(mutable_database):
# Dont add any package definitions to this repository, the idea is that
# packages should not have to be defined in the repository once they
# are installed
with spack.repo.swap(MockPackageMultiRepo([])):
with spack.repo.swap(MockPackageMultiRepo()):
spack.store.store.reindex()
_check_db_sanity(mutable_database)

View file

@ -12,7 +12,7 @@
from spack.spec import Spec
from spack.dependency import all_deptypes, Dependency, canonical_deptype
from spack.test.conftest import MockPackage, MockPackageMultiRepo
from spack.util.mock_package import MockPackageMultiRepo
def check_links(spec_to_check):
@ -69,12 +69,12 @@ def test_test_deptype():
default = ('build', 'link')
test_only = ('test',)
x = MockPackage('x', [], [])
z = MockPackage('z', [], [])
y = MockPackage('y', [z], [test_only])
w = MockPackage('w', [x, y], [test_only, default])
mock_repo = MockPackageMultiRepo()
x = mock_repo.add_package('x', [], [])
z = mock_repo.add_package('z', [], [])
y = mock_repo.add_package('y', [z], [test_only])
w = mock_repo.add_package('w', [x, y], [test_only, default])
mock_repo = MockPackageMultiRepo([w, x, y, z])
with spack.repo.swap(mock_repo):
spec = Spec('w')
spec.concretize(tests=(w.name,))
@ -93,8 +93,9 @@ def test_installed_deps():
default = ('build', 'link')
build_only = ('build',)
e = MockPackage('e', [], [])
d = MockPackage('d', [], [])
mock_repo = MockPackageMultiRepo()
e = mock_repo.add_package('e', [], [])
d = mock_repo.add_package('d', [], [])
c_conditions = {
d.name: {
'c': 'd@2'
@ -103,11 +104,10 @@ def test_installed_deps():
'c': 'e@2'
}
}
c = MockPackage('c', [d, e], [build_only, default],
conditions=c_conditions)
b = MockPackage('b', [d, e], [default, default])
a = MockPackage('a', [b, c], [default, default])
mock_repo = MockPackageMultiRepo([a, b, c, d, e])
c = mock_repo.add_package('c', [d, e], [build_only, default],
conditions=c_conditions)
b = mock_repo.add_package('b', [d, e], [default, default])
mock_repo.add_package('a', [b, c], [default, default])
with spack.repo.swap(mock_repo):
c_spec = Spec('c')
@ -133,10 +133,10 @@ def test_specify_preinstalled_dep():
"""
default = ('build', 'link')
c = MockPackage('c', [], [])
b = MockPackage('b', [c], [default])
a = MockPackage('a', [b], [default])
mock_repo = MockPackageMultiRepo([a, b, c])
mock_repo = MockPackageMultiRepo()
c = mock_repo.add_package('c', [], [])
b = mock_repo.add_package('b', [c], [default])
mock_repo.add_package('a', [b], [default])
with spack.repo.swap(mock_repo):
b_spec = Spec('b')
@ -161,15 +161,15 @@ def test_conditional_dep_with_user_constraints():
"""
default = ('build', 'link')
y = MockPackage('y', [], [])
mock_repo = MockPackageMultiRepo()
y = mock_repo.add_package('y', [], [])
x_on_y_conditions = {
y.name: {
'x@2:': 'y'
}
}
x = MockPackage('x', [y], [default], conditions=x_on_y_conditions)
mock_repo.add_package('x', [y], [default], conditions=x_on_y_conditions)
mock_repo = MockPackageMultiRepo([x, y])
with spack.repo.swap(mock_repo):
spec = Spec('x ^y@2')
spec.concretize()

View file

@ -26,7 +26,7 @@
from spack import repo
from spack.spec import Spec, save_dependency_spec_yamls
from spack.util.spack_yaml import syaml_dict
from spack.test.conftest import MockPackage, MockPackageMultiRepo
from spack.util.mock_package import MockPackageMultiRepo
def check_yaml_round_trip(spec):
@ -301,15 +301,14 @@ def test_save_dependency_spec_yamls_subset(tmpdir, config):
default = ('build', 'link')
g = MockPackage('g', [], [])
f = MockPackage('f', [], [])
e = MockPackage('e', [], [])
d = MockPackage('d', [f, g], [default, default])
c = MockPackage('c', [], [])
b = MockPackage('b', [d, e], [default, default])
a = MockPackage('a', [b, c], [default, default])
mock_repo = MockPackageMultiRepo([a, b, c, d, e, f, g])
mock_repo = MockPackageMultiRepo()
g = mock_repo.add_package('g', [], [])
f = mock_repo.add_package('f', [], [])
e = mock_repo.add_package('e', [], [])
d = mock_repo.add_package('d', [f, g], [default, default])
c = mock_repo.add_package('c', [], [])
b = mock_repo.add_package('b', [d, e], [default, default])
mock_repo.add_package('a', [b, c], [default, default])
with repo.swap(mock_repo):
spec_a = Spec('a')

View file

@ -0,0 +1,43 @@
# Copyright 2013-2020 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)
import spack.repo
from spack.util.mock_package import MockPackageMultiRepo
def test_mock_package_possible_dependencies():
mock_repo = MockPackageMultiRepo()
e = mock_repo.add_package('e')
d = mock_repo.add_package('d', [e])
c = mock_repo.add_package('c', [d])
b = mock_repo.add_package('b', [d])
a = mock_repo.add_package('a', [b, c])
with spack.repo.swap(mock_repo):
assert set(a.possible_dependencies()) == set(['a', 'b', 'c', 'd', 'e'])
assert set(b.possible_dependencies()) == set(['b', 'd', 'e'])
assert set(c.possible_dependencies()) == set(['c', 'd', 'e'])
assert set(d.possible_dependencies()) == set(['d', 'e'])
assert set(e.possible_dependencies()) == set(['e'])
assert set(
a.possible_dependencies(transitive=False)) == set(['a', 'b', 'c'])
assert set(
b.possible_dependencies(transitive=False)) == set(['b', 'd'])
assert set(
c.possible_dependencies(transitive=False)) == set(['c', 'd'])
assert set(
d.possible_dependencies(transitive=False)) == set(['d', 'e'])
assert set(
e.possible_dependencies(transitive=False)) == set(['e'])
def test_mock_repo_is_virtual():
mock_repo = MockPackageMultiRepo()
# current implementation is always false
assert mock_repo.is_virtual("foo") is False
assert mock_repo.is_virtual("bar") is False
assert mock_repo.is_virtual("baz") is False

View file

@ -0,0 +1,161 @@
# Copyright 2013-2020 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 ordereddict_backport
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.
"""
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
class MockPackageMultiRepo(object):
"""Mock package repository, mimicking ``spack.repo.Repo``."""
def __init__(self):
self.spec_to_pkg = {}
def get(self, spec):
if not isinstance(spec, spack.spec.Spec):
spec = Spec(spec)
return self.spec_to_pkg[spec.name]
def get_pkg_class(self, name):
return self.spec_to_pkg[name]
def exists(self, name):
return name in self.spec_to_pkg
def is_virtual(self, name):
return False
def repo_for_pkg(self, name):
import collections
Repo = collections.namedtuple('Repo', ['namespace'])
return Repo('mockrepo')
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 = ordereddict_backport.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