From 1c8f792bb5e3fd96d2cfb341d988e42923956f3f Mon Sep 17 00:00:00 2001 From: Tamara Dahlgren <35777542+tldahlgren@users.noreply.github.com> Date: Thu, 12 Mar 2020 13:20:20 -0700 Subject: testing: increase installer coverage (#15237) --- lib/spack/spack/installer.py | 51 ++++---- lib/spack/spack/test/install.py | 69 +++++++--- lib/spack/spack/test/installer.py | 269 ++++++++++++++++++++++++++++---------- 3 files changed, 280 insertions(+), 109 deletions(-) diff --git a/lib/spack/spack/installer.py b/lib/spack/spack/installer.py index 4559fd70ba..4e8ec39733 100755 --- a/lib/spack/spack/installer.py +++ b/lib/spack/spack/installer.py @@ -36,6 +36,7 @@ import six import sys import time +import llnl.util.filesystem as fs import llnl.util.lock as lk import llnl.util.tty as tty import spack.binary_distribution as binary_distribution @@ -43,14 +44,12 @@ import spack.compilers import spack.error import spack.hooks import spack.package +import spack.package_prefs as prefs import spack.repo import spack.store -from llnl.util.filesystem import \ - chgrp, install, install_tree, mkdirp, touch, working_dir from llnl.util.tty.color import colorize, cwrite from llnl.util.tty.log import log_output -from spack.package_prefs import get_package_dir_permissions, get_package_group from spack.util.environment import dump_environment from spack.util.executable import which @@ -133,21 +132,21 @@ def _do_fake_install(pkg): chmod = which('chmod') # Install fake command - mkdirp(pkg.prefix.bin) - touch(os.path.join(pkg.prefix.bin, command)) + fs.mkdirp(pkg.prefix.bin) + fs.touch(os.path.join(pkg.prefix.bin, command)) chmod('+x', os.path.join(pkg.prefix.bin, command)) # Install fake header file - mkdirp(pkg.prefix.include) - touch(os.path.join(pkg.prefix.include, header + '.h')) + fs.mkdirp(pkg.prefix.include) + fs.touch(os.path.join(pkg.prefix.include, header + '.h')) # Install fake shared and static libraries - mkdirp(pkg.prefix.lib) + fs.mkdirp(pkg.prefix.lib) for suffix in [dso_suffix, '.a']: - touch(os.path.join(pkg.prefix.lib, library + suffix)) + fs.touch(os.path.join(pkg.prefix.lib, library + suffix)) # Install fake man page - mkdirp(pkg.prefix.man.man1) + fs.mkdirp(pkg.prefix.man.man1) packages_dir = spack.store.layout.build_packages_path(pkg.spec) dump_packages(pkg.spec, packages_dir) @@ -377,14 +376,14 @@ def dump_packages(spec, path): Dump all package information for a spec and its dependencies. This creates a package repository within path for every namespace in the - spec DAG, and fills the repos wtih package files and patch files for every + spec DAG, and fills the repos with package files and patch files for every node in the DAG. Args: spec (Spec): the Spack spec whose package information is to be dumped path (str): the path to the build packages directory """ - mkdirp(path) + fs.mkdirp(path) # Copy in package.py files from any dependencies. # Note that we copy them in as they are in the *install* directory @@ -425,7 +424,7 @@ def dump_packages(spec, path): if node is spec: spack.repo.path.dump_provenance(node, dest_pkg_dir) elif source_pkg_dir: - install_tree(source_pkg_dir, dest_pkg_dir) + fs.install_tree(source_pkg_dir, dest_pkg_dir) def install_msg(name, pid): @@ -461,17 +460,17 @@ def log(pkg): tty.debug(e) # Archive the whole stdout + stderr for the package - install(pkg.log_path, pkg.install_log_path) + fs.install(pkg.log_path, pkg.install_log_path) # Archive the environment used for the build - install(pkg.env_path, pkg.install_env_path) + fs.install(pkg.env_path, pkg.install_env_path) if os.path.exists(pkg.configure_args_path): # Archive the args used for the build - install(pkg.configure_args_path, pkg.install_configure_args_path) + fs.install(pkg.configure_args_path, pkg.install_configure_args_path) # Finally, archive files that are specific to each package - with working_dir(pkg.stage.path): + with fs.working_dir(pkg.stage.path): errors = six.StringIO() target_dir = os.path.join( spack.store.layout.metadata_path(pkg.spec), 'archived-files') @@ -493,8 +492,8 @@ def log(pkg): target = os.path.join(target_dir, f) # We must ensure that the directory exists before # copying a file in - mkdirp(os.path.dirname(target)) - install(f, target) + fs.mkdirp(os.path.dirname(target)) + fs.install(f, target) except Exception as e: tty.debug(e) @@ -505,7 +504,7 @@ def log(pkg): if errors.getvalue(): error_file = os.path.join(target_dir, 'errors.txt') - mkdirp(target_dir) + fs.mkdirp(target_dir) with open(error_file, 'w') as err: err.write(errors.getvalue()) tty.warn('Errors occurred when archiving files.\n\t' @@ -1071,10 +1070,10 @@ class PackageInstaller(object): pkg.name, 'src') tty.msg('{0} Copying source to {1}' .format(pre, src_target)) - install_tree(pkg.stage.source_path, src_target) + fs.install_tree(pkg.stage.source_path, src_target) # Do the real install in the source directory. - with working_dir(pkg.stage.source_path): + with fs.working_dir(pkg.stage.source_path): # Save the build environment in a file before building. dump_environment(pkg.env_path) @@ -1286,20 +1285,20 @@ class PackageInstaller(object): spack.store.layout.create_install_directory(pkg.spec) else: # Set the proper group for the prefix - group = get_package_group(pkg.spec) + group = prefs.get_package_group(pkg.spec) if group: - chgrp(pkg.spec.prefix, group) + fs.chgrp(pkg.spec.prefix, group) # Set the proper permissions. # This has to be done after group because changing groups blows # away the sticky group bit on the directory mode = os.stat(pkg.spec.prefix).st_mode - perms = get_package_dir_permissions(pkg.spec) + perms = prefs.get_package_dir_permissions(pkg.spec) if mode != perms: os.chmod(pkg.spec.prefix, perms) # Ensure the metadata path exists as well - mkdirp(spack.store.layout.metadata_path(pkg.spec), mode=perms) + fs.mkdirp(spack.store.layout.metadata_path(pkg.spec), mode=perms) def _update_failed(self, task, mark=False, exc=None): """ diff --git a/lib/spack/spack/test/install.py b/lib/spack/spack/test/install.py index ff7f5a3355..2f779c6a5f 100644 --- a/lib/spack/spack/test/install.py +++ b/lib/spack/spack/test/install.py @@ -7,7 +7,7 @@ import os import pytest import shutil -from llnl.util.filesystem import mkdirp, touch, working_dir +import llnl.util.filesystem as fs from spack.package import InstallError, PackageBase, PackageStillNeededError import spack.error @@ -380,11 +380,11 @@ def test_pkg_build_paths(install_mockery): # Backward compatibility checks log_dir = os.path.dirname(log_path) - mkdirp(log_dir) - with working_dir(log_dir): + fs.mkdirp(log_dir) + with fs.working_dir(log_dir): # Start with the older of the previous log filenames older_log = 'spack-build.out' - touch(older_log) + fs.touch(older_log) assert spec.package.log_path.endswith(older_log) # Now check the newer log filename @@ -416,11 +416,11 @@ def test_pkg_install_paths(install_mockery): # Backward compatibility checks log_dir = os.path.dirname(log_path) - mkdirp(log_dir) - with working_dir(log_dir): + fs.mkdirp(log_dir) + with fs.working_dir(log_dir): # Start with the older of the previous install log filenames older_log = 'build.out' - touch(older_log) + fs.touch(older_log) assert spec.package.install_log_path.endswith(older_log) # Now check the newer install log filename @@ -437,7 +437,8 @@ def test_pkg_install_paths(install_mockery): shutil.rmtree(log_dir) -def test_pkg_install_log(install_mockery): +def test_log_install_without_build_files(install_mockery): + """Test the installer log function when no build files are present.""" # Get a basic concrete spec for the trivial install package. spec = Spec('trivial-install-test-package').concretized() @@ -445,17 +446,40 @@ def test_pkg_install_log(install_mockery): with pytest.raises(IOError, match="No such file or directory"): spack.installer.log(spec.package) - # Set up mock build files and try again + +def test_log_install_with_build_files(install_mockery, monkeypatch): + """Test the installer's log function when have build files.""" + config_log = 'config.log' + + # Retain the original function for use in the monkey patch that is used + # to raise an exception under the desired condition for test coverage. + orig_install_fn = fs.install + + def _install(src, dest): + orig_install_fn(src, dest) + if src.endswith(config_log): + raise Exception('Mock log install error') + + monkeypatch.setattr(fs, 'install', _install) + + spec = Spec('trivial-install-test-package').concretized() + + # Set up mock build files and try again to include archive failure log_path = spec.package.log_path log_dir = os.path.dirname(log_path) - mkdirp(log_dir) - with working_dir(log_dir): - touch(log_path) - touch(spec.package.env_path) - touch(spec.package.configure_args_path) + fs.mkdirp(log_dir) + with fs.working_dir(log_dir): + fs.touch(log_path) + fs.touch(spec.package.env_path) + fs.touch(spec.package.configure_args_path) install_path = os.path.dirname(spec.package.install_log_path) - mkdirp(install_path) + fs.mkdirp(install_path) + + source = spec.package.stage.source_path + config = os.path.join(source, 'config.log') + fs.touchp(config) + spec.package.archive_files = ['missing', '..', config] spack.installer.log(spec.package) @@ -463,6 +487,21 @@ def test_pkg_install_log(install_mockery): assert os.path.exists(spec.package.install_env_path) assert os.path.exists(spec.package.install_configure_args_path) + archive_dir = os.path.join(install_path, 'archived-files') + source_dir = os.path.dirname(source) + rel_config = os.path.relpath(config, source_dir) + + assert os.path.exists(os.path.join(archive_dir, rel_config)) + assert not os.path.exists(os.path.join(archive_dir, 'missing')) + + expected_errs = [ + 'OUTSIDE SOURCE PATH', # for '..' + 'FAILED TO ARCHIVE' # for rel_config + ] + with open(os.path.join(archive_dir, 'errors.txt'), 'r') as fd: + for ln, expected in zip(fd, expected_errs): + assert expected in ln + # Cleanup shutil.rmtree(log_dir) diff --git a/lib/spack/spack/test/installer.py b/lib/spack/spack/test/installer.py index b9730c3165..0be4bc78c0 100644 --- a/lib/spack/spack/test/installer.py +++ b/lib/spack/spack/test/installer.py @@ -7,15 +7,19 @@ import os import py import pytest +import llnl.util.filesystem as fs import llnl.util.tty as tty +import llnl.util.lock as ulk import spack.binary_distribution import spack.compilers import spack.directory_layout as dl import spack.installer as inst -import spack.util.lock as lk +import spack.package_prefs as prefs import spack.repo import spack.spec +import spack.store +import spack.util.lock as lk def _mock_repo(root, namespace): @@ -152,7 +156,7 @@ def test_process_external_package_module(install_mockery, monkeypatch, capfd): def test_process_binary_cache_tarball_none(install_mockery, monkeypatch, capfd): - """Tests to cover _process_binary_cache_tarball when no tarball.""" + """Tests of _process_binary_cache_tarball when no tarball.""" monkeypatch.setattr(spack.binary_distribution, 'download_tarball', _none) pkg = spack.repo.get('trivial-install-test-package') @@ -162,7 +166,7 @@ def test_process_binary_cache_tarball_none(install_mockery, monkeypatch, def test_process_binary_cache_tarball_tar(install_mockery, monkeypatch, capfd): - """Tests to cover _process_binary_cache_tarball with a tar file.""" + """Tests of _process_binary_cache_tarball with a tar file.""" def _spec(spec): return spec @@ -179,6 +183,25 @@ def test_process_binary_cache_tarball_tar(install_mockery, monkeypatch, capfd): assert 'Installing a from binary cache' in capfd.readouterr()[0] +def test_try_install_from_binary_cache(install_mockery, mock_packages, + monkeypatch, capsys): + """Tests SystemExit path for_try_install_from_binary_cache.""" + def _spec(spec, force): + spec = spack.spec.Spec('mpi').concretized() + return {spec: None} + + spec = spack.spec.Spec('mpich') + spec.concretize() + + monkeypatch.setattr(spack.binary_distribution, 'get_spec', _spec) + + with pytest.raises(SystemExit): + inst._try_install_from_binary_cache(spec.package, False, False) + + captured = capsys.readouterr() + assert 'add a spack mirror to allow download' in str(captured) + + def test_installer_init_errors(install_mockery): """Test to ensure cover installer constructor errors.""" with pytest.raises(ValueError, match='must be a package'): @@ -189,17 +212,18 @@ def test_installer_init_errors(install_mockery): inst.PackageInstaller(pkg) -def test_installer_strings(install_mockery): - """Tests of installer repr and str for coverage purposes.""" +def test_installer_repr(install_mockery): spec, installer = create_installer('trivial-install-test-package') - # Cover __repr__ irep = installer.__repr__() assert irep.startswith(installer.__class__.__name__) assert "installed=" in irep assert "failed=" in irep - # Cover __str__ + +def test_installer_str(install_mockery): + spec, installer = create_installer('trivial-install-test-package') + istr = str(installer) assert "#tasks=0" in istr assert "installed (0)" in istr @@ -207,7 +231,6 @@ def test_installer_strings(install_mockery): def test_installer_last_phase_error(install_mockery, capsys): - """Test to cover last phase error.""" spec = spack.spec.Spec('trivial-install-test-package') spec.concretize() assert spec.concrete @@ -220,7 +243,6 @@ def test_installer_last_phase_error(install_mockery, capsys): def test_installer_ensure_ready_errors(install_mockery): - """Test to cover _ensure_ready errors.""" spec, installer = create_installer('trivial-install-test-package') fmt = r'cannot be installed locally.*{0}' @@ -247,24 +269,102 @@ def test_installer_ensure_ready_errors(install_mockery): installer._ensure_install_ready(spec.package) -def test_ensure_locked_have(install_mockery, tmpdir): - """Test to cover _ensure_locked when already have lock.""" +def test_ensure_locked_err(install_mockery, monkeypatch, tmpdir, capsys): + """Test _ensure_locked when a non-lock exception is raised.""" + mock_err_msg = 'Mock exception error' + + def _raise(lock, timeout): + raise RuntimeError(mock_err_msg) + spec, installer = create_installer('trivial-install-test-package') + monkeypatch.setattr(ulk.Lock, 'acquire_read', _raise) with tmpdir.as_cwd(): + with pytest.raises(RuntimeError): + installer._ensure_locked('read', spec.package) + + out = str(capsys.readouterr()[1]) + assert 'Failed to acquire a read lock' in out + assert mock_err_msg in out + + +def test_ensure_locked_have(install_mockery, tmpdir, capsys): + """Test _ensure_locked when already have lock.""" + spec, installer = create_installer('trivial-install-test-package') + + with tmpdir.as_cwd(): + # Test "downgrade" of a read lock (to a read lock) lock = lk.Lock('./test', default_timeout=1e-9, desc='test') lock_type = 'read' tpl = (lock_type, lock) installer.locks[installer.pkg_id] = tpl assert installer._ensure_locked(lock_type, spec.package) == tpl + # Test "upgrade" of a read lock without read count to a write + lock_type = 'write' + err = 'Cannot upgrade lock' + with pytest.raises(ulk.LockUpgradeError, match=err): + installer._ensure_locked(lock_type, spec.package) + + out = str(capsys.readouterr()[1]) + assert 'Failed to upgrade to a write lock' in out + assert 'exception when releasing read lock' in out + + # Test "upgrade" of the read lock *with* read count to a write + lock._reads = 1 + tpl = (lock_type, lock) + assert installer._ensure_locked(lock_type, spec.package) == tpl + + # Test "downgrade" of the write lock to a read lock + lock_type = 'read' + tpl = (lock_type, lock) + assert installer._ensure_locked(lock_type, spec.package) == tpl + + +@pytest.mark.parametrize('lock_type,reads,writes', [ + ('read', 1, 0), + ('write', 0, 1)]) +def test_ensure_locked_new_lock( + install_mockery, tmpdir, lock_type, reads, writes): + pkg_id = 'a' + spec, installer = create_installer(pkg_id) + with tmpdir.as_cwd(): + ltype, lock = installer._ensure_locked(lock_type, spec.package) + assert ltype == lock_type + assert lock is not None + assert lock._reads == reads + assert lock._writes == writes + + +def test_ensure_locked_new_warn(install_mockery, monkeypatch, tmpdir, capsys): + orig_pl = spack.database.Database.prefix_lock + + def _pl(db, spec, timeout): + lock = orig_pl(db, spec, timeout) + lock.default_timeout = 1e-9 if timeout is None else None + return lock + + pkg_id = 'a' + spec, installer = create_installer(pkg_id) + + monkeypatch.setattr(spack.database.Database, 'prefix_lock', _pl) -def test_package_id(install_mockery): - """Test to cover package_id functionality.""" + lock_type = 'read' + ltype, lock = installer._ensure_locked(lock_type, spec.package) + assert ltype == lock_type + assert lock is not None + + out = str(capsys.readouterr()[1]) + assert 'Expected prefix lock timeout' in out + + +def test_package_id_err(install_mockery): pkg = spack.repo.get('trivial-install-test-package') with pytest.raises(ValueError, match='spec is not concretized'): inst.package_id(pkg) + +def test_package_id_ok(install_mockery): spec = spack.spec.Spec('trivial-install-test-package') spec.concretize() assert spec.concrete @@ -273,36 +373,44 @@ def test_package_id(install_mockery): def test_fake_install(install_mockery): - """Test to cover fake install basics.""" spec = spack.spec.Spec('trivial-install-test-package') spec.concretize() assert spec.concrete + pkg = spec.package inst._do_fake_install(pkg) assert os.path.isdir(pkg.prefix.lib) -def test_packages_needed_to_bootstrap_compiler(install_mockery, monkeypatch): - """Test to cover most of _packages_needed_to_boostrap_compiler.""" - # TODO: More work is needed to go beyond the dependency check - def _no_compilers(pkg, arch_spec): - return [] - - # Test path where no compiler packages returned +def test_packages_needed_to_bootstrap_compiler_none(install_mockery): spec = spack.spec.Spec('trivial-install-test-package') spec.concretize() assert spec.concrete + packages = inst._packages_needed_to_bootstrap_compiler(spec.package) assert not packages - # Test up to the dependency check - monkeypatch.setattr(spack.compilers, 'compilers_for_spec', _no_compilers) - with pytest.raises(spack.repo.UnknownPackageError, match='not found'): - inst._packages_needed_to_bootstrap_compiler(spec.package) + +def test_packages_needed_to_bootstrap_compiler_packages(install_mockery, + monkeypatch): + spec = spack.spec.Spec('trivial-install-test-package') + spec.concretize() + + def _conc_spec(compiler): + return spack.spec.Spec('a').concretized() + + # Ensure we can get past functions that are precluding obtaining + # packages. + monkeypatch.setattr(spack.compilers, 'compilers_for_spec', _none) + monkeypatch.setattr(spack.compilers, 'pkg_spec_for_compiler', _conc_spec) + monkeypatch.setattr(spack.spec.Spec, 'concretize', _noop) + + packages = inst._packages_needed_to_bootstrap_compiler(spec.package) + assert packages def test_dump_packages_deps_ok(install_mockery, tmpdir, mock_repo_path): - """Test to add coverage to dump_packages with dependencies happy path.""" + """Test happy path for dump_packages with dependencies.""" spec_name = 'simple-inheritance' spec = spack.spec.Spec(spec_name).concretized() @@ -314,7 +422,7 @@ def test_dump_packages_deps_ok(install_mockery, tmpdir, mock_repo_path): def test_dump_packages_deps_errs(install_mockery, tmpdir, monkeypatch, capsys): - """Test to add coverage to dump_packages with dependencies.""" + """Test error paths for dump_packages with dependencies.""" orig_bpp = spack.store.layout.build_packages_path orig_dirname = spack.repo.Repo.dirname_for_package_name repo_err_msg = "Mock dirname_for_package_name" @@ -354,59 +462,45 @@ def test_dump_packages_deps_errs(install_mockery, tmpdir, monkeypatch, capsys): assert "Couldn't copy in provenance for cmake" in out -@pytest.mark.tld -def test_check_deps_status_errs(install_mockery, monkeypatch): - """Test to cover _check_deps_status failures.""" +def test_check_deps_status_install_failure(install_mockery, monkeypatch): spec, installer = create_installer('a') # Make sure the package is identified as failed - orig_fn = spack.database.Database.prefix_failed monkeypatch.setattr(spack.database.Database, 'prefix_failed', _true) with pytest.raises(inst.InstallError, match='install failure'): installer._check_deps_status() - monkeypatch.setattr(spack.database.Database, 'prefix_failed', orig_fn) - # Ensure do not acquire the lock +def test_check_deps_status_write_locked(install_mockery, monkeypatch): + spec, installer = create_installer('a') + + # Ensure the lock is not acquired monkeypatch.setattr(inst.PackageInstaller, '_ensure_locked', _not_locked) with pytest.raises(inst.InstallError, match='write locked by another'): installer._check_deps_status() -@pytest.mark.tld def test_check_deps_status_external(install_mockery, monkeypatch): - """Test to cover _check_deps_status for external.""" spec, installer = create_installer('a') - deps = spec.dependencies() - assert len(deps) > 0 - dep_id = 'b' - - # Ensure the known dependent is installed if flagged as external + # Mock the known dependent, b, as external so assumed to be installed monkeypatch.setattr(spack.spec.Spec, 'external', True) installer._check_deps_status() - assert dep_id in installer.installed + assert 'b' in installer.installed -@pytest.mark.tld def test_check_deps_status_upstream(install_mockery, monkeypatch): - """Test to cover _check_deps_status for upstream.""" spec, installer = create_installer('a') - deps = spec.dependencies() - assert len(deps) > 0 - dep_id = 'b' - - # Ensure the known dependent, b, is installed if flagged as upstream + # Mock the known dependent, b, as installed upstream monkeypatch.setattr(spack.package.PackageBase, 'installed_upstream', True) installer._check_deps_status() - assert dep_id in installer.installed + assert 'b' in installer.installed def test_add_bootstrap_compilers(install_mockery, monkeypatch): - """Test to cover _add_bootstrap_compilers.""" def _pkgs(pkg): spec = spack.spec.Spec('mpi').concretized() return [(spec.package, True)] @@ -445,7 +539,6 @@ def test_installer_init_queue(install_mockery): def test_install_task_use_cache(install_mockery, monkeypatch): - """Test _install_task to cover use_cache path.""" spec, installer = create_installer('trivial-install-test-package') task = create_build_task(spec.package) @@ -454,25 +547,27 @@ def test_install_task_use_cache(install_mockery, monkeypatch): assert spec.package.name in installer.installed -def test_install_task_stop_iter(install_mockery, monkeypatch, capfd): - """Test _install_task to cover the StopIteration exception.""" - mock_err_msg = 'mock stop iteration' +def test_install_task_add_compiler(install_mockery, monkeypatch, capfd): + config_msg = 'mock add_compilers_to_config' - def _raise(installer, pkg): - raise StopIteration(mock_err_msg) + def _add(_compilers): + tty.msg(config_msg) spec, installer = create_installer('a') task = create_build_task(spec.package) + task.compiler = True + # Preclude any meaningful side-effects monkeypatch.setattr(spack.package.PackageBase, 'unit_test_check', _true) - monkeypatch.setattr(inst.PackageInstaller, '_setup_install_dir', _raise) + monkeypatch.setattr(inst.PackageInstaller, '_setup_install_dir', _noop) + monkeypatch.setattr(spack.build_environment, 'fork', _noop) + monkeypatch.setattr(spack.database.Database, 'add', _noop) + monkeypatch.setattr(spack.compilers, 'add_compilers_to_config', _add) installer._install_task(task) - out = capfd.readouterr()[0] - assert mock_err_msg in out - assert 'Package stage directory' in out - assert spec.package.stage.source_path in out + out = capfd.readouterr()[0] + assert config_msg in out def test_release_lock_write_n_exception(install_mockery, tmpdir, capsys): @@ -529,8 +624,36 @@ def test_cleanup_all_tasks(install_mockery, monkeypatch): assert len(installer.build_tasks) == 1 -def test_cleanup_failed(install_mockery, tmpdir, monkeypatch, capsys): - """Test to increase coverage of _cleanup_failed.""" +def test_setup_install_dir_grp(install_mockery, monkeypatch, capfd): + """Test _setup_install_dir's group change.""" + mock_group = 'mockgroup' + mock_chgrp_msg = 'Changing group for {0} to {1}' + + def _get_group(spec): + return mock_group + + def _chgrp(path, group): + tty.msg(mock_chgrp_msg.format(path, group)) + + monkeypatch.setattr(prefs, 'get_package_group', _get_group) + monkeypatch.setattr(fs, 'chgrp', _chgrp) + + spec, installer = create_installer('trivial-install-test-package') + + fs.touchp(spec.prefix) + metadatadir = spack.store.layout.metadata_path(spec) + # Should fail with a "not a directory" error + with pytest.raises(OSError, match=metadatadir): + installer._setup_install_dir(spec.package) + + out = str(capfd.readouterr()[0]) + + expected_msg = mock_chgrp_msg.format(spec.prefix, mock_group) + assert expected_msg in out + + +def test_cleanup_failed_err(install_mockery, tmpdir, monkeypatch, capsys): + """Test _cleanup_failed exception path.""" msg = 'Fake release_write exception' def _raise_except(lock): @@ -550,13 +673,14 @@ def test_cleanup_failed(install_mockery, tmpdir, monkeypatch, capsys): assert msg in out -def test_update_failed_no_mark(install_mockery): - """Test of _update_failed sans mark and dependent build tasks.""" +def test_update_failed_no_dependent_task(install_mockery): + """Test _update_failed with missing dependent build tasks.""" spec, installer = create_installer('dependent-install') - task = create_build_task(spec.package) - installer._update_failed(task) - assert installer.failed['dependent-install'] is None + for dep in spec.traverse(root=False): + task = create_build_task(dep.package) + installer._update_failed(task, mark=False) + assert installer.failed[task.pkg_id] is None def test_install_uninstalled_deps(install_mockery, monkeypatch, capsys): @@ -710,3 +834,12 @@ def test_install_dir_exists(install_mockery, monkeypatch, capfd): installer.install() assert 'b' in installer.installed + + +def test_install_skip_patch(install_mockery, mock_fetch): + """Test the path skip_patch install path.""" + spec, installer = create_installer('b') + + installer.install(fake=False, skip_patch=True) + + assert 'b' in installer.installed -- cgit v1.2.3-60-g2f50