Add test deptype (#5132)

* Add '--test=all' and '--test=root' options to test either the root or the root and all dependencies.
* add a test dependency type that is only used when --test is enabled.
* test dependencies are not added to the spec, but they are provided in the test environment.
This commit is contained in:
scheibelp 2017-09-29 22:08:15 -07:00 committed by Todd Gamblin
parent 3c0e799a51
commit 9e7faff6c9
10 changed files with 182 additions and 13 deletions

View File

@ -158,6 +158,9 @@
build_jobs = _config.get('build_jobs', multiprocessing.cpu_count())
package_testing = spack.package_prefs.PackageTesting()
#-----------------------------------------------------------------------------
# When packages call 'from spack import *', this extra stuff is brought in.
#

View File

@ -191,9 +191,9 @@ def set_build_environment_variables(pkg, env, dirty):
dirty (bool): Skip unsetting the user's environment settings
"""
# Gather information about various types of dependencies
build_deps = pkg.spec.dependencies(deptype='build')
build_deps = pkg.spec.dependencies(deptype=('build', 'test'))
link_deps = pkg.spec.traverse(root=False, deptype=('link'))
build_link_deps = pkg.spec.traverse(root=False, deptype=('build', 'link'))
build_link_deps = list(build_deps) + list(link_deps)
rpath_deps = get_rpath_deps(pkg)
build_prefixes = [dep.prefix for dep in build_deps]

View File

@ -97,9 +97,18 @@ def setup_parser(subparser):
nargs=argparse.REMAINDER,
help="spec of the package to install"
)
subparser.add_argument(
testing = subparser.add_mutually_exclusive_group()
testing.add_argument(
'--test', default=None,
choices=['root', 'all'],
help="""If 'root' is chosen, run package tests during
installation for top-level packages (but skip tests for dependencies).
if 'all' is chosen, run package tests during installation for all
packages. If neither are chosen, don't run tests for any packages."""
)
testing.add_argument(
'--run-tests', action='store_true',
help="run package level tests during installation"
help='run package tests during installation (same as --test=all)'
)
subparser.add_argument(
'--log-format',
@ -325,12 +334,21 @@ def install(parser, args, **kwargs):
'install_source': args.install_source,
'install_deps': 'dependencies' in args.things_to_install,
'make_jobs': args.jobs,
'run_tests': args.run_tests,
'verbose': args.verbose,
'fake': args.fake,
'dirty': args.dirty
})
if args.run_tests:
tty.warn("Deprecated option: --run-tests: use --test=all instead")
specs = spack.cmd.parse_specs(args.package)
if args.test == 'all' or args.run_tests:
spack.package_testing.test_all()
elif args.test == 'root':
for spec in specs:
spack.package_testing.test(spec.name)
# Spec from cli
specs = []
if args.file:

View File

@ -1210,7 +1210,6 @@ def do_install(self,
skip_patch=False,
verbose=False,
make_jobs=None,
run_tests=False,
fake=False,
explicit=False,
dirty=None,
@ -1235,7 +1234,6 @@ def do_install(self,
suppresses it)
make_jobs (int): Number of make jobs to use for install. Default
is ncpus
run_tests (bool): Run tests within the package's install()
fake (bool): Don't really build; install fake stub files instead.
explicit (bool): True if package was explicitly installed, False
if package was implicitly installed (as a dependency).
@ -1281,7 +1279,6 @@ def do_install(self,
skip_patch=skip_patch,
verbose=verbose,
make_jobs=make_jobs,
run_tests=run_tests,
dirty=dirty,
**kwargs
)
@ -1289,7 +1286,7 @@ def do_install(self,
tty.msg('Installing %s' % self.name)
# Set run_tests flag before starting build.
self.run_tests = run_tests
self.run_tests = spack.package_testing.check(self.name)
# Set parallelism before starting build.
self.make_jobs = make_jobs

View File

@ -204,6 +204,25 @@ def preferred_variants(cls, pkg_name):
if name in pkg.variants)
class PackageTesting(object):
def __init__(self):
self.packages_to_test = set()
self._test_all = False
def test(self, package_name):
self.packages_to_test.add(package_name)
def test_all(self):
self._test_all = True
def clear(self):
self._test_all = False
self.packages_to_test.clear()
def check(self, package_name):
return self._test_all or (package_name in self.packages_to_test)
def spec_externals(spec):
"""Return a list of external specs (w/external directory path filled in),
one for each known external installation."""

View File

@ -197,7 +197,7 @@
_any_version = VersionList([':'])
#: Types of dependencies that Spack understands.
alldeps = ('build', 'link', 'run')
alldeps = ('build', 'link', 'run', 'test')
#: Max integer helps avoid passing too large a value to cyaml.
maxint = 2 ** (ctypes.sizeof(ctypes.c_int) * 8 - 1) - 1
@ -2089,7 +2089,8 @@ def _normalize_helper(self, visited, spec_deps, provider_index):
pkg_dep = self._evaluate_dependency_conditions(dep_name)
deptypes = pkg.dependency_types[dep_name]
# If pkg_dep is a dependency, merge it.
if pkg_dep:
if pkg_dep and (spack.package_testing.check(self.name) or
set(deptypes) - set(['test'])):
changed |= self._merge_dependency(
pkg_dep, deptypes, visited, spec_deps, provider_index)
any_change |= changed

View File

@ -44,6 +44,15 @@ def parser():
return parser
@pytest.fixture()
def noop_install(monkeypatch):
def noop(*args, **kwargs):
return
monkeypatch.setattr(spack.package.PackageBase, 'do_install', noop)
def test_install_package_and_dependency(
tmpdir, builtin_mock, mock_archive, mock_fetch, config,
install_mockery):
@ -64,6 +73,33 @@ def test_install_package_and_dependency(
assert not spack.repo.get(s).stage.created
@pytest.mark.usefixtures('noop_install', 'builtin_mock', 'config')
def test_install_runtests():
assert not spack.package_testing._test_all
assert not spack.package_testing.packages_to_test
install('--test=root', 'dttop')
assert not spack.package_testing._test_all
assert spack.package_testing.packages_to_test == set(['dttop'])
spack.package_testing.clear()
install('--test=all', 'a')
assert spack.package_testing._test_all
assert not spack.package_testing.packages_to_test
spack.package_testing.clear()
install('--run-tests', 'a')
assert spack.package_testing._test_all
assert not spack.package_testing.packages_to_test
spack.package_testing.clear()
assert not spack.package_testing._test_all
assert not spack.package_testing.packages_to_test
def test_install_package_already_installed(
tmpdir, builtin_mock, mock_archive, mock_fetch, config,
install_mockery):

View File

@ -44,6 +44,8 @@
import spack.util.pattern
from spack.package import PackageBase
from spack.fetch_strategy import *
from spack.spec import Spec
from spack.version import Version
##########
@ -552,3 +554,58 @@ def get_rev():
t = Bunch(checks=checks, url=url, hash=get_rev, path=str(repodir))
yield t
current.chdir()
##########
# Mock packages
##########
class MockPackage(object):
def __init__(self, name, dependencies, dependency_types, conditions=None,
versions=None):
self.name = name
self.spec = None
dep_to_conditions = ordereddict_backport.OrderedDict()
for dep in dependencies:
if not conditions or dep.name not in conditions:
dep_to_conditions[dep.name] = {name: dep.name}
else:
dep_to_conditions[dep.name] = conditions[dep.name]
self.dependencies = dep_to_conditions
self.dependency_types = dict(
(x.name, y) for x, y in zip(dependencies, dependency_types))
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 = {}
class MockPackageMultiRepo(object):
def __init__(self, packages):
self.specToPkg = dict((x.name, x) for x in packages)
def get(self, spec):
if not isinstance(spec, spack.spec.Spec):
spec = Spec(spec)
return self.specToPkg[spec.name]
def get_pkg_class(self, name):
return self.specToPkg[name]
def exists(self, name):
return name in self.specToPkg
def is_virtual(self, name):
return False
def repo_for_pkg(self, name):
import collections
Repo = collections.namedtuple('Repo', ['namespace'])
return Repo('mockrepo')

View File

@ -30,6 +30,7 @@
import spack.architecture
import spack.package
from spack.test.conftest import MockPackage, MockPackageMultiRepo
from spack.spec import Spec, canonical_deptype, alldeps
@ -69,6 +70,44 @@ def _mock(pkg_name, spec, deptypes=spack.alldeps):
return _mock
@pytest.mark.usefixtures('config')
def test_test_deptype():
"""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)
"""
saved_repo = spack.repo
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([w, x, y, z])
try:
spack.package_testing.test(w.name)
spack.repo = mock_repo
spec = Spec('w')
spec.concretize()
assert ('x' in spec)
assert ('z' not in spec)
finally:
spack.repo = saved_repo
spack.package_testing.clear()
@pytest.mark.usefixtures('refresh_builtin_mock')
class TestSpecDag(object):

View File

@ -39,8 +39,7 @@ class Libpsl(AutotoolsPackage):
depends_on('pkg-config@0.9.0:', type='build')
depends_on('python@2.7:', type='build')
# TODO: Add a 'test' deptype
# depends_on('valgrind', type='test')
depends_on('valgrind~mpi~boost', type='test')
def configure_args(self):
spec = self.spec