init: remove package_testing global

- refactor the way test dependencies are passed to the concretizer
- remove global state
- update tests
This commit is contained in:
Todd Gamblin 2018-04-27 14:41:12 -07:00 committed by scheibelp
parent bc9f5f084f
commit 47dc96224d
9 changed files with 82 additions and 75 deletions

View File

@ -43,11 +43,6 @@
tty.die('while initializing Spack RepoPath:', e.message) tty.die('while initializing Spack RepoPath:', e.message)
#: Needed for test dependencies
from spack.package_prefs import PackageTesting
package_testing = PackageTesting()
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
# When packages call 'from spack import *', we import a set of things that # When packages call 'from spack import *', we import a set of things that
# should be useful for builds. # should be useful for builds.

View File

@ -139,14 +139,15 @@ def parse_specs(args, **kwargs):
""" """
concretize = kwargs.get('concretize', False) concretize = kwargs.get('concretize', False)
normalize = kwargs.get('normalize', False) normalize = kwargs.get('normalize', False)
tests = kwargs.get('tests', False)
try: try:
specs = spack.spec.parse(args) specs = spack.spec.parse(args)
for spec in specs: for spec in specs:
if concretize: if concretize:
spec.concretize() # implies normalize spec.concretize(tests=tests) # implies normalize
elif normalize: elif normalize:
spec.normalize() spec.normalize(tests=tests)
return specs return specs

View File

@ -50,11 +50,6 @@ def setup_parser(subparser):
cd_group = subparser.add_mutually_exclusive_group() cd_group = subparser.add_mutually_exclusive_group()
arguments.add_common_arguments(cd_group, ['clean', 'dirty']) arguments.add_common_arguments(cd_group, ['clean', 'dirty'])
subparser.add_argument(
'--run-tests', action='store_true', dest='run_tests',
help="run package level tests during installation"
)
def bootstrap(parser, args, **kwargs): def bootstrap(parser, args, **kwargs):
kwargs.update({ kwargs.update({
@ -62,7 +57,6 @@ def bootstrap(parser, args, **kwargs):
'keep_stage': args.keep_stage, 'keep_stage': args.keep_stage,
'install_deps': 'dependencies', 'install_deps': 'dependencies',
'make_jobs': args.jobs, 'make_jobs': args.jobs,
'run_tests': args.run_tests,
'verbose': args.verbose, 'verbose': args.verbose,
'dirty': args.dirty 'dirty': args.dirty
}) })

View File

@ -202,14 +202,16 @@ def install(parser, args, **kwargs):
reporter.filename = args.log_file reporter.filename = args.log_file
specs = spack.cmd.parse_specs(args.package) specs = spack.cmd.parse_specs(args.package)
tests = False
if args.test == 'all' or args.run_tests: if args.test == 'all' or args.run_tests:
spack.package_testing.test_all() tests = True
elif args.test == 'root': elif args.test == 'root':
for spec in specs: tests = [spec.name for spec in specs]
spack.package_testing.test(spec.name) kwargs['tests'] = tests
try: try:
specs = spack.cmd.parse_specs(args.package, concretize=True) specs = spack.cmd.parse_specs(
args.package, concretize=True, tests=tests)
except SpackError as e: except SpackError as e:
reporter.concretization_report(e.message) reporter.concretization_report(e.message)
raise raise

View File

@ -1380,6 +1380,7 @@ def do_install(self,
make_jobs=None, make_jobs=None,
fake=False, fake=False,
explicit=False, explicit=False,
tests=False,
dirty=None, dirty=None,
**kwargs): **kwargs):
"""Called by commands to install a package and its dependencies. """Called by commands to install a package and its dependencies.
@ -1405,6 +1406,8 @@ def do_install(self,
fake (bool): Don't really build; install fake stub files instead. fake (bool): Don't really build; install fake stub files instead.
explicit (bool): True if package was explicitly installed, False explicit (bool): True if package was explicitly installed, False
if package was implicitly installed (as a dependency). if package was implicitly installed (as a dependency).
tests (bool or list or set): False to run no tests, True to test
all packages, or a list of package names to run tests for some
dirty (bool): Don't clean the build environment before installing. dirty (bool): Don't clean the build environment before installing.
force (bool): Install again, even if already installed. force (bool): Install again, even if already installed.
""" """
@ -1435,7 +1438,6 @@ def do_install(self,
# is installed # is installed
if keep_stage is False: if keep_stage is False:
self.stage.destroy() self.stage.destroy()
return self._update_explicit_entry_in_db(rec, explicit) return self._update_explicit_entry_in_db(rec, explicit)
self._do_install_pop_kwargs(kwargs) self._do_install_pop_kwargs(kwargs)
@ -1454,6 +1456,7 @@ def do_install(self,
skip_patch=skip_patch, skip_patch=skip_patch,
verbose=verbose, verbose=verbose,
make_jobs=make_jobs, make_jobs=make_jobs,
tests=tests,
dirty=dirty, dirty=dirty,
**kwargs) **kwargs)
@ -1470,8 +1473,9 @@ def do_install(self,
tty.msg('No binary for %s found: installing from source' tty.msg('No binary for %s found: installing from source'
% self.name) % self.name)
# Set run_tests flag before starting build. # Set run_tests flag before starting build
self.run_tests = spack.package_testing.check(self.name) self.run_tests = (tests is True or
tests and self.name in tests)
# Set parallelism before starting build. # Set parallelism before starting build.
self.make_jobs = make_jobs self.make_jobs = make_jobs
@ -1555,6 +1559,11 @@ def build_process():
# preserve verbosity across runs # preserve verbosity across runs
return echo return echo
# hook that allow tests to inspect this Package before installation
# see unit_test_check() docs.
if not self.unit_test_check():
return
try: try:
# Create the install prefix and fork the build process. # Create the install prefix and fork the build process.
if not os.path.exists(self.prefix): if not os.path.exists(self.prefix):
@ -1594,6 +1603,23 @@ def build_process():
# check the filesystem for it. # check the filesystem for it.
self.stage.created = False self.stage.created = False
def unit_test_check(self):
"""Hook for unit tests to assert things about package internals.
Unit tests can override this function to perform checks after
``Package.install`` and all post-install hooks run, but before
the database is updated.
The overridden function may indicate that the install procedure
should terminate early (before updating the database) by
returning ``False`` (or any value such that ``bool(result)`` is
``False``).
Return:
(bool): ``True`` to continue, ``False`` to skip ``install()``
"""
return True
def check_for_unfinished_installation( def check_for_unfinished_installation(
self, keep_prefix=False, restage=False): self, keep_prefix=False, restage=False):
"""Check for leftover files from partially-completed prior install to """Check for leftover files from partially-completed prior install to

View File

@ -204,25 +204,6 @@ def preferred_variants(cls, pkg_name):
if name in pkg.variants) 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): def spec_externals(spec):
"""Return a list of external specs (w/external directory path filled in), """Return a list of external specs (w/external directory path filled in),
one for each known external installation.""" one for each known external installation."""

View File

@ -1808,10 +1808,14 @@ def feq(cfield, sfield):
return changed return changed
def concretize(self): def concretize(self, tests=False):
"""A spec is concrete if it describes one build of a package uniquely. """A spec is concrete if it describes one build of a package uniquely.
This will ensure that this spec is concrete. This will ensure that this spec is concrete.
Args:
tests (list or bool): list of packages that will need test
dependencies, or True/False for test all/none
If this spec could describe more than one version, variant, or build If this spec could describe more than one version, variant, or build
of a package, this will add constraints to make it concrete. of a package, this will add constraints to make it concrete.
@ -1830,7 +1834,7 @@ def concretize(self):
force = False force = False
while changed: while changed:
changes = (self.normalize(force), changes = (self.normalize(force, tests),
self._expand_virtual_packages(), self._expand_virtual_packages(),
self._concretize_helper()) self._concretize_helper())
changed = any(changes) changed = any(changes)
@ -2050,7 +2054,7 @@ def _find_provider(self, vdep, provider_index):
raise UnsatisfiableProviderSpecError(required[0], vdep) raise UnsatisfiableProviderSpecError(required[0], vdep)
def _merge_dependency( def _merge_dependency(
self, dependency, visited, spec_deps, provider_index): self, dependency, visited, spec_deps, provider_index, tests):
"""Merge dependency information from a Package into this Spec. """Merge dependency information from a Package into this Spec.
Args: Args:
@ -2146,10 +2150,10 @@ def _merge_dependency(
self._add_dependency(spec_dependency, dependency.type) self._add_dependency(spec_dependency, dependency.type)
changed |= spec_dependency._normalize_helper( changed |= spec_dependency._normalize_helper(
visited, spec_deps, provider_index) visited, spec_deps, provider_index, tests)
return changed return changed
def _normalize_helper(self, visited, spec_deps, provider_index): def _normalize_helper(self, visited, spec_deps, provider_index, tests):
"""Recursive helper function for _normalize.""" """Recursive helper function for _normalize."""
if self.name in visited: if self.name in visited:
return False return False
@ -2170,16 +2174,23 @@ def _normalize_helper(self, visited, spec_deps, provider_index):
for dep_name in self.package_class.dependencies: for dep_name in self.package_class.dependencies:
# Do we depend on dep_name? If so pkg_dep is not None. # Do we depend on dep_name? If so pkg_dep is not None.
dep = self._evaluate_dependency_conditions(dep_name) dep = self._evaluate_dependency_conditions(dep_name)
# If dep is a needed dependency, merge it. # If dep is a needed dependency, merge it.
if dep and (spack.package_testing.check(self.name) or if dep:
set(dep.type) - set(['test'])): merge = (
changed |= self._merge_dependency( # caller requested test dependencies
dep, visited, spec_deps, provider_index) tests is True or (tests and self.name in tests) or
# this is not a test-only dependency
dep.type - set(['test']))
if merge:
changed |= self._merge_dependency(
dep, visited, spec_deps, provider_index, tests)
any_change |= changed any_change |= changed
return any_change return any_change
def normalize(self, force=False): def normalize(self, force=False, tests=False):
"""When specs are parsed, any dependencies specified are hanging off """When specs are parsed, any dependencies specified are hanging off
the root, and ONLY the ones that were explicitly provided are there. the root, and ONLY the ones that were explicitly provided are there.
Normalization turns a partial flat spec into a DAG, where: Normalization turns a partial flat spec into a DAG, where:
@ -2220,7 +2231,8 @@ def normalize(self, force=False):
# to package files & their 'when' specs # to package files & their 'when' specs
visited = set() visited = set()
any_change = self._normalize_helper(visited, spec_deps, provider_index) any_change = self._normalize_helper(
visited, spec_deps, provider_index, tests)
# If there are deps specified but not visited, they're not # If there are deps specified but not visited, they're not
# actually deps of this package. Raise an error. # actually deps of this package. Raise an error.

View File

@ -53,10 +53,8 @@ def parser():
@pytest.fixture() @pytest.fixture()
def noop_install(monkeypatch): def noop_install(monkeypatch):
def noop(*args, **kwargs): def noop(*args, **kwargs):
return pass
monkeypatch.setattr(spack.package.PackageBase, 'do_install', noop) monkeypatch.setattr(spack.package.PackageBase, 'do_install', noop)
@ -77,31 +75,31 @@ def test_install_package_and_dependency(
assert 'errors="0"' in content assert 'errors="0"' in content
@pytest.mark.usefixtures('noop_install', 'builtin_mock', 'config') @pytest.mark.disable_clean_stage_check
def test_install_runtests(): def test_install_runtests_notests(monkeypatch, builtin_mock, install_mockery):
assert not spack.package_testing._test_all def check(pkg):
assert not spack.package_testing.packages_to_test assert not pkg.run_tests
monkeypatch.setattr(spack.package.PackageBase, 'unit_test_check', check)
install('-v', 'dttop')
@pytest.mark.disable_clean_stage_check
def test_install_runtests_root(monkeypatch, builtin_mock, install_mockery):
def check(pkg):
assert pkg.run_tests == (pkg.name == 'dttop')
monkeypatch.setattr(spack.package.PackageBase, 'unit_test_check', check)
install('--test=root', 'dttop') install('--test=root', 'dttop')
assert not spack.package_testing._test_all
assert spack.package_testing.packages_to_test == set(['dttop'])
spack.package_testing.clear()
@pytest.mark.disable_clean_stage_check
def test_install_runtests_all(monkeypatch, builtin_mock, install_mockery):
def check(pkg):
assert pkg.run_tests
monkeypatch.setattr(spack.package.PackageBase, 'unit_test_check', check)
install('--test=all', 'a') 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') 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( def test_install_package_already_installed(

View File

@ -98,16 +98,14 @@ def test_test_deptype():
mock_repo = MockPackageMultiRepo([w, x, y, z]) mock_repo = MockPackageMultiRepo([w, x, y, z])
try: try:
spack.package_testing.test(w.name)
spack.repo = mock_repo spack.repo = mock_repo
spec = Spec('w') spec = Spec('w')
spec.concretize() spec.concretize(tests=(w.name,))
assert ('x' in spec) assert ('x' in spec)
assert ('z' not in spec) assert ('z' not in spec)
finally: finally:
spack.repo = saved_repo spack.repo = saved_repo
spack.package_testing.clear()
@pytest.mark.usefixtures('refresh_builtin_mock') @pytest.mark.usefixtures('refresh_builtin_mock')