spack/lib/spack/spack/test/cmd/install.py
Massimiliano Culpo 7841978488
Turn the compiler wrapper into a package
Remove the compiler wrappers from core Spack, and move
the relevant code into the "compiler-wrapper" package.
2025-01-11 13:57:52 +01:00

1096 lines
38 KiB
Python

# Copyright Spack Project Developers. See COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import argparse
import builtins
import filecmp
import gzip
import itertools
import os
import pathlib
import re
import time
import pytest
import llnl.util.filesystem as fs
import llnl.util.tty as tty
import spack.build_environment
import spack.cmd.common.arguments
import spack.cmd.install
import spack.config
import spack.environment as ev
import spack.error
import spack.hash_types as ht
import spack.installer
import spack.package_base
import spack.store
from spack.error import SpackError, SpecSyntaxError
from spack.installer import PackageInstaller
from spack.main import SpackCommand
from spack.spec import Spec
install = SpackCommand("install")
env = SpackCommand("env")
add = SpackCommand("add")
mirror = SpackCommand("mirror")
uninstall = SpackCommand("uninstall")
buildcache = SpackCommand("buildcache")
find = SpackCommand("find")
# @pytest.fixture(autouse=True)
# def gcc_runtime_mock_install(mock_packages, monkeypatch):
# import spack.pkg.builtin.mock.gcc_runtime
#
# def _mock_install(self, spec, prefix):
# mkdir(prefix.lib)
#
# monkeypatch.setattr(spack.pkg.builtin.mock.gcc_runtime.GccRuntime, "install", _mock_install)
@pytest.fixture()
def noop_install(monkeypatch):
def noop(*args, **kwargs):
pass
monkeypatch.setattr(spack.installer.PackageInstaller, "install", noop)
def test_install_package_and_dependency(
tmpdir, mock_packages, mock_archive, mock_fetch, install_mockery
):
log = "test"
with tmpdir.as_cwd():
install("--fake", "--log-format=junit", f"--log-file={log}", "libdwarf")
files = tmpdir.listdir()
filename = tmpdir.join(f"{log}.xml")
assert filename in files
content = filename.open().read()
assert 'tests="4"' in content
assert 'failures="0"' in content
assert 'errors="0"' in content
@pytest.mark.disable_clean_stage_check
def test_install_runtests_notests(monkeypatch, mock_packages, install_mockery):
def check(pkg):
assert not pkg.run_tests
monkeypatch.setattr(spack.package_base.PackageBase, "unit_test_check", check)
install("-v", "dttop")
@pytest.mark.disable_clean_stage_check
def test_install_runtests_root(monkeypatch, mock_packages, install_mockery):
def check(pkg):
assert pkg.run_tests == (pkg.name == "dttop")
monkeypatch.setattr(spack.package_base.PackageBase, "unit_test_check", check)
install("--test=root", "dttop")
@pytest.mark.disable_clean_stage_check
def test_install_runtests_all(monkeypatch, mock_packages, install_mockery):
def check(pkg):
assert pkg.run_tests
monkeypatch.setattr(spack.package_base.PackageBase, "unit_test_check", check)
install("--test=all", "pkg-a")
def test_install_package_already_installed(
tmpdir, mock_packages, mock_archive, mock_fetch, install_mockery
):
with tmpdir.as_cwd():
install("--fake", "libdwarf")
install("--fake", "--log-format=junit", "--log-file=test.xml", "libdwarf")
files = tmpdir.listdir()
filename = tmpdir.join("test.xml")
assert filename in files
content = filename.open().read()
print(content)
assert 'tests="5"' in content
assert 'failures="0"' in content
assert 'errors="0"' in content
skipped = [line for line in content.split("\n") if "skipped" in line]
assert len(skipped) == 5
@pytest.mark.parametrize(
"arguments,expected",
[
([], spack.config.get("config:dirty")), # default from config file
(["--clean"], False),
(["--dirty"], True),
],
)
def test_install_dirty_flag(arguments, expected):
parser = argparse.ArgumentParser()
spack.cmd.install.setup_parser(parser)
args = parser.parse_args(arguments)
assert args.dirty == expected
def test_package_output(tmpdir, capsys, install_mockery, mock_fetch):
"""
Ensure output printed from pkgs is captured by output redirection.
"""
# we can't use output capture here because it interferes with Spack's
# logging. TODO: see whether we can get multiple log_outputs to work
# when nested AND in pytest
spec = Spec("printing-package").concretized()
pkg = spec.package
PackageInstaller([pkg], explicit=True, verbose=True).install()
with gzip.open(pkg.install_log_path, "rt") as f:
out = f.read()
# make sure that output from the actual package file appears in the
# right place in the build log.
assert "BEFORE INSTALL" in out
assert "AFTER INSTALL" in out
@pytest.mark.disable_clean_stage_check
def test_install_output_on_build_error(
mock_packages, mock_archive, mock_fetch, install_mockery, capfd
):
"""
This test used to assume receiving full output, but since we've updated
spack to generate logs on the level of phases, it will only return the
last phase, install.
"""
# capfd interferes with Spack's capturing
with capfd.disabled():
out = install("-v", "build-error", fail_on_error=False)
assert "Installing build-error" in out
@pytest.mark.disable_clean_stage_check
def test_install_output_on_python_error(mock_packages, mock_archive, mock_fetch, install_mockery):
out = install("failing-build", fail_on_error=False)
assert isinstance(install.error, spack.build_environment.ChildError)
assert install.error.name == "InstallError"
assert 'raise InstallError("Expected failure.")' in out
@pytest.mark.disable_clean_stage_check
def test_install_with_source(mock_packages, mock_archive, mock_fetch, install_mockery):
"""Verify that source has been copied into place."""
install("--source", "--keep-stage", "trivial-install-test-package")
spec = Spec("trivial-install-test-package").concretized()
src = os.path.join(spec.prefix.share, "trivial-install-test-package", "src")
assert filecmp.cmp(
os.path.join(mock_archive.path, "configure"), os.path.join(src, "configure")
)
def test_install_env_variables(mock_packages, mock_archive, mock_fetch, install_mockery):
spec = Spec("pkg-c").concretized()
install("pkg-c")
assert os.path.isfile(spec.package.install_env_path)
@pytest.mark.disable_clean_stage_check
def test_show_log_on_error(mock_packages, mock_archive, mock_fetch, install_mockery, capfd):
"""
Make sure --show-log-on-error works.
"""
with capfd.disabled():
out = install("--show-log-on-error", "build-error", fail_on_error=False)
assert isinstance(install.error, spack.build_environment.ChildError)
assert install.error.pkg.name == "build-error"
assert "Installing build-error" in out
assert "See build log for details:" in out
def test_install_overwrite(mock_packages, mock_archive, mock_fetch, install_mockery):
"""Tests installing a spec, and then re-installing it in the same prefix."""
spec = Spec("pkg-c").concretized()
install("pkg-c")
# Ignore manifest and install times
manifest = os.path.join(
spec.prefix,
spack.store.STORE.layout.metadata_dir,
spack.store.STORE.layout.manifest_file_name,
)
ignores = [manifest, spec.package.times_log_path]
assert os.path.exists(spec.prefix)
expected_md5 = fs.hash_directory(spec.prefix, ignore=ignores)
# Modify the first installation to be sure the content is not the same
# as the one after we reinstalled
with open(os.path.join(spec.prefix, "only_in_old"), "w", encoding="utf-8") as f:
f.write("This content is here to differentiate installations.")
bad_md5 = fs.hash_directory(spec.prefix, ignore=ignores)
assert bad_md5 != expected_md5
install("--overwrite", "-y", "pkg-c")
assert os.path.exists(spec.prefix)
assert fs.hash_directory(spec.prefix, ignore=ignores) == expected_md5
assert fs.hash_directory(spec.prefix, ignore=ignores) != bad_md5
def test_install_overwrite_not_installed(mock_packages, mock_archive, mock_fetch, install_mockery):
"""Tests that overwrite doesn't fail if the package is not installed"""
spec = Spec("pkg-c").concretized()
assert not os.path.exists(spec.prefix)
install("--overwrite", "-y", "pkg-c")
assert os.path.exists(spec.prefix)
def test_install_commit(mock_git_version_info, install_mockery, mock_packages, monkeypatch):
"""Test installing a git package from a commit.
This ensures Spack associates commit versions with their packages in time to do
version lookups. Details of version lookup tested elsewhere.
"""
repo_path, filename, commits = mock_git_version_info
file_url = pathlib.Path(repo_path).as_uri()
monkeypatch.setattr(spack.package_base.PackageBase, "git", file_url, raising=False)
# Use the earliest commit in the respository
spec = Spec(f"git-test-commit@{commits[-1]}").concretized()
PackageInstaller([spec.package], explicit=True).install()
# Ensure first commit file contents were written
installed = os.listdir(spec.prefix.bin)
assert filename in installed
with open(spec.prefix.bin.join(filename), "r", encoding="utf-8") as f:
content = f.read().strip()
assert content == "[0]" # contents are weird for another test
def test_install_overwrite_multiple(mock_packages, mock_archive, mock_fetch, install_mockery):
# Try to install a spec and then to reinstall it.
libdwarf = Spec("libdwarf").concretized()
cmake = Spec("cmake").concretized()
install("--fake", "libdwarf")
install("--fake", "cmake")
ld_manifest = os.path.join(
libdwarf.prefix,
spack.store.STORE.layout.metadata_dir,
spack.store.STORE.layout.manifest_file_name,
)
ld_ignores = [ld_manifest, libdwarf.package.times_log_path]
assert os.path.exists(libdwarf.prefix)
expected_libdwarf_md5 = fs.hash_directory(libdwarf.prefix, ignore=ld_ignores)
cm_manifest = os.path.join(
cmake.prefix,
spack.store.STORE.layout.metadata_dir,
spack.store.STORE.layout.manifest_file_name,
)
cm_ignores = [cm_manifest, cmake.package.times_log_path]
assert os.path.exists(cmake.prefix)
expected_cmake_md5 = fs.hash_directory(cmake.prefix, ignore=cm_ignores)
# Modify the first installation to be sure the content is not the same
# as the one after we reinstalled
with open(os.path.join(libdwarf.prefix, "only_in_old"), "w", encoding="utf-8") as f:
f.write("This content is here to differentiate installations.")
with open(os.path.join(cmake.prefix, "only_in_old"), "w", encoding="utf-8") as f:
f.write("This content is here to differentiate installations.")
bad_libdwarf_md5 = fs.hash_directory(libdwarf.prefix, ignore=ld_ignores)
bad_cmake_md5 = fs.hash_directory(cmake.prefix, ignore=cm_ignores)
assert bad_libdwarf_md5 != expected_libdwarf_md5
assert bad_cmake_md5 != expected_cmake_md5
install("--fake", "--overwrite", "-y", "libdwarf", "cmake")
assert os.path.exists(libdwarf.prefix)
assert os.path.exists(cmake.prefix)
ld_hash = fs.hash_directory(libdwarf.prefix, ignore=ld_ignores)
cm_hash = fs.hash_directory(cmake.prefix, ignore=cm_ignores)
assert ld_hash == expected_libdwarf_md5
assert cm_hash == expected_cmake_md5
assert ld_hash != bad_libdwarf_md5
assert cm_hash != bad_cmake_md5
@pytest.mark.usefixtures("mock_packages", "mock_archive", "mock_fetch", "install_mockery")
def test_install_conflicts(conflict_spec):
# Make sure that spec with conflicts raises a SpackError
with pytest.raises(SpackError):
install(conflict_spec)
@pytest.mark.usefixtures("mock_packages", "mock_archive", "mock_fetch", "install_mockery")
def test_install_invalid_spec():
# Make sure that invalid specs raise a SpackError
with pytest.raises(SpecSyntaxError, match="unexpected characters"):
install("conflict%~")
@pytest.mark.usefixtures("noop_install", "mock_packages", "config")
@pytest.mark.parametrize(
"spec,concretize,error_code",
[
(Spec("mpi"), False, 1),
(Spec("mpi"), True, 0),
(Spec("boost"), False, 1),
(Spec("boost"), True, 0),
],
)
def test_install_from_file(spec, concretize, error_code, tmpdir):
if concretize:
spec.concretize()
specfile = tmpdir.join("spec.yaml")
with specfile.open("w") as f:
spec.to_yaml(f)
err_msg = "does not contain a concrete spec" if error_code else ""
# Relative path to specfile (regression for #6906)
with fs.working_dir(specfile.dirname):
# A non-concrete spec will fail to be installed
out = install("-f", specfile.basename, fail_on_error=False)
assert install.returncode == error_code
assert err_msg in out
# Absolute path to specfile (regression for #6983)
out = install("-f", str(specfile), fail_on_error=False)
assert install.returncode == error_code
assert err_msg in out
@pytest.mark.disable_clean_stage_check
@pytest.mark.usefixtures("mock_packages", "mock_archive", "mock_fetch", "install_mockery")
@pytest.mark.parametrize(
"exc_typename,msg",
[("RuntimeError", "something weird happened"), ("ValueError", "spec is not concrete")],
)
def test_junit_output_with_failures(tmpdir, exc_typename, msg):
with tmpdir.as_cwd():
install(
"--log-format=junit",
"--log-file=test.xml",
"raiser",
"exc_type={0}".format(exc_typename),
'msg="{0}"'.format(msg),
fail_on_error=False,
)
assert isinstance(install.error, spack.build_environment.ChildError)
assert install.error.name == exc_typename
assert install.error.pkg.name == "raiser"
files = tmpdir.listdir()
filename = tmpdir.join("test.xml")
assert filename in files
content = filename.open().read()
# Count failures and errors correctly
assert 'tests="1"' in content
assert 'failures="1"' in content
assert 'errors="0"' in content
# Nothing should have succeeded
assert 'tests="0"' not in content
assert 'failures="0"' not in content
# We want to have both stdout and stderr
assert "<system-out>" in content
assert msg in content
@pytest.mark.disable_clean_stage_check
@pytest.mark.parametrize(
"exc_typename,expected_exc,msg",
[
("RuntimeError", spack.error.InstallError, "something weird happened"),
("KeyboardInterrupt", KeyboardInterrupt, "Ctrl-C strikes again"),
],
)
def test_junit_output_with_errors(
exc_typename,
expected_exc,
msg,
mock_packages,
mock_archive,
mock_fetch,
install_mockery,
tmpdir,
monkeypatch,
):
def just_throw(*args, **kwargs):
exc_type = getattr(builtins, exc_typename)
raise exc_type(msg)
monkeypatch.setattr(spack.installer.PackageInstaller, "_install_task", just_throw)
with tmpdir.as_cwd():
install("--log-format=junit", "--log-file=test.xml", "libdwarf", fail_on_error=False)
assert isinstance(install.error, expected_exc)
files = tmpdir.listdir()
filename = tmpdir.join("test.xml")
assert filename in files
content = filename.open().read()
# Only libelf error is reported (through libdwarf root spec). libdwarf
# install is skipped and it is not an error.
assert 'tests="0"' not in content
assert 'failures="0"' in content
assert 'errors="0"' not in content
# Nothing should have succeeded
assert 'errors="0"' not in content
# We want to have both stdout and stderr
assert "<system-out>" in content
assert f'error message="{msg}"' in content
@pytest.mark.usefixtures("noop_install", "mock_packages", "config")
@pytest.mark.parametrize(
"clispecs,filespecs",
[
[[], ["mpi"]],
[[], ["mpi", "boost"]],
[["cmake"], ["mpi"]],
[["cmake", "libelf"], []],
[["cmake", "libelf"], ["mpi", "boost"]],
],
)
def test_install_mix_cli_and_files(clispecs, filespecs, tmpdir):
args = clispecs
for spec in filespecs:
filepath = tmpdir.join(spec + ".yaml")
args = ["-f", str(filepath)] + args
s = Spec(spec)
s.concretize()
with filepath.open("w") as f:
s.to_yaml(f)
install(*args, fail_on_error=False)
assert install.returncode == 0
def test_extra_files_are_archived(mock_packages, mock_archive, mock_fetch, install_mockery):
s = Spec("archive-files")
s.concretize()
install("archive-files")
archive_dir = os.path.join(spack.store.STORE.layout.metadata_path(s), "archived-files")
config_log = os.path.join(archive_dir, mock_archive.expanded_archive_basedir, "config.log")
assert os.path.exists(config_log)
errors_txt = os.path.join(archive_dir, "errors.txt")
assert os.path.exists(errors_txt)
@pytest.mark.disable_clean_stage_check
def test_cdash_report_concretization_error(
tmpdir, mock_fetch, install_mockery, capfd, conflict_spec
):
# capfd interferes with Spack's capturing
with capfd.disabled():
with tmpdir.as_cwd():
with pytest.raises(SpackError):
install("--log-format=cdash", "--log-file=cdash_reports", conflict_spec)
report_dir = tmpdir.join("cdash_reports")
assert report_dir in tmpdir.listdir()
report_file = report_dir.join("Update.xml")
assert report_file in report_dir.listdir()
content = report_file.open().read()
assert "<UpdateReturnStatus>" in content
# The message is different based on using the
# new or the old concretizer
expected_messages = ("Conflicts in concretized spec", "conflicts with")
assert any(x in content for x in expected_messages)
@pytest.mark.not_on_windows("Windows log_output logs phase header out of order")
@pytest.mark.disable_clean_stage_check
def test_cdash_upload_build_error(tmpdir, mock_fetch, install_mockery, capfd):
# capfd interferes with Spack's capturing
with capfd.disabled():
with tmpdir.as_cwd():
with pytest.raises(SpackError):
install(
"--log-format=cdash",
"--log-file=cdash_reports",
"--cdash-upload-url=http://localhost/fakeurl/submit.php?project=Spack",
"build-error",
)
report_dir = tmpdir.join("cdash_reports")
assert report_dir in tmpdir.listdir()
report_file = report_dir.join("Build.xml")
assert report_file in report_dir.listdir()
content = report_file.open().read()
assert "<Text>configure: error: in /path/to/some/file:</Text>" in content
@pytest.mark.disable_clean_stage_check
def test_cdash_upload_clean_build(tmpdir, mock_fetch, install_mockery, capfd):
# capfd interferes with Spack's capturing of e.g., Build.xml output
with capfd.disabled(), tmpdir.as_cwd():
install("--log-file=cdash_reports", "--log-format=cdash", "pkg-c")
report_dir = tmpdir.join("cdash_reports")
assert report_dir in tmpdir.listdir()
report_file = report_dir.join("Build.xml")
assert report_file in report_dir.listdir()
content = report_file.open().read()
assert "</Build>" in content
assert "<Text>" not in content
@pytest.mark.disable_clean_stage_check
def test_cdash_upload_extra_params(tmpdir, mock_fetch, install_mockery, capfd):
# capfd interferes with Spack's capture of e.g., Build.xml output
with capfd.disabled(), tmpdir.as_cwd():
install(
"--log-file=cdash_reports",
"--log-format=cdash",
"--cdash-build=my_custom_build",
"--cdash-site=my_custom_site",
"--cdash-track=my_custom_track",
"pkg-c",
)
report_dir = tmpdir.join("cdash_reports")
assert report_dir in tmpdir.listdir()
report_file = report_dir.join("Build.xml")
assert report_file in report_dir.listdir()
content = report_file.open().read()
assert 'Site BuildName="my_custom_build"' in content
assert 'Name="my_custom_site"' in content
assert "-my_custom_track" in content
@pytest.mark.disable_clean_stage_check
def test_cdash_buildstamp_param(tmpdir, mock_fetch, install_mockery, capfd):
# capfd interferes with Spack's capture of e.g., Build.xml output
with capfd.disabled(), tmpdir.as_cwd():
cdash_track = "some_mocked_track"
buildstamp_format = f"%Y%m%d-%H%M-{cdash_track}"
buildstamp = time.strftime(buildstamp_format, time.localtime(int(time.time())))
install(
"--log-file=cdash_reports",
"--log-format=cdash",
f"--cdash-buildstamp={buildstamp}",
"pkg-c",
)
report_dir = tmpdir.join("cdash_reports")
assert report_dir in tmpdir.listdir()
report_file = report_dir.join("Build.xml")
assert report_file in report_dir.listdir()
content = report_file.open().read()
assert buildstamp in content
@pytest.mark.disable_clean_stage_check
def test_cdash_install_from_spec_json(
tmpdir, mock_fetch, install_mockery, capfd, mock_packages, mock_archive
):
# capfd interferes with Spack's capturing
with capfd.disabled(), tmpdir.as_cwd():
spec_json_path = str(tmpdir.join("spec.json"))
pkg_spec = Spec("pkg-c").concretized()
with open(spec_json_path, "w", encoding="utf-8") as fd:
fd.write(pkg_spec.to_json(hash=ht.dag_hash))
install(
"--log-format=cdash",
"--log-file=cdash_reports",
"--cdash-build=my_custom_build",
"--cdash-site=my_custom_site",
"--cdash-track=my_custom_track",
"-f",
spec_json_path,
)
report_dir = tmpdir.join("cdash_reports")
assert report_dir in tmpdir.listdir()
report_file = report_dir.join("Configure.xml")
assert report_file in report_dir.listdir()
content = report_file.open().read()
install_command_regex = re.compile(
r"<ConfigureCommand>(.+)</ConfigureCommand>", re.MULTILINE | re.DOTALL
)
m = install_command_regex.search(content)
assert m
install_command = m.group(1)
assert "pkg-c@" in install_command
@pytest.mark.disable_clean_stage_check
def test_build_error_output(tmpdir, mock_fetch, install_mockery, capfd):
with capfd.disabled():
msg = ""
try:
install("build-error")
assert False, "no exception was raised!"
except spack.build_environment.ChildError as e:
msg = e.long_message
assert "configure: error: in /path/to/some/file:" in msg
assert "configure: error: cannot run C compiled programs." in msg
@pytest.mark.disable_clean_stage_check
def test_build_warning_output(tmpdir, mock_fetch, install_mockery, capfd):
with capfd.disabled():
msg = ""
try:
install("build-warnings")
assert False, "no exception was raised!"
except spack.build_environment.ChildError as e:
msg = e.long_message
assert "WARNING: ALL CAPITAL WARNING!" in msg
assert "foo.c:89: warning: some weird warning!" in msg
def test_cache_only_fails(tmpdir, mock_fetch, install_mockery, capfd):
# libelf from cache fails to install, which automatically removes the
# the libdwarf build task
with capfd.disabled():
out = install("--cache-only", "libdwarf", fail_on_error=False)
assert "Failed to install gcc-runtime" in out
assert "Skipping build of libdwarf" in out
assert "was not installed" in out
# Check that failure prefix locks are still cached
failed_packages = [
pkg_name for dag_hash, pkg_name in spack.store.STORE.failure_tracker.locker.locks.keys()
]
assert "libelf" in failed_packages
assert "libdwarf" in failed_packages
def test_install_only_dependencies(tmpdir, mock_fetch, install_mockery):
dep = Spec("dependency-install").concretized()
root = Spec("dependent-install").concretized()
install("--only", "dependencies", "dependent-install")
assert os.path.exists(dep.prefix)
assert not os.path.exists(root.prefix)
def test_install_only_package(tmpdir, mock_fetch, install_mockery, capfd):
msg = ""
with capfd.disabled():
try:
install("--only", "package", "dependent-install")
except spack.error.InstallError as e:
msg = str(e)
assert "Cannot proceed with dependent-install" in msg
assert "1 uninstalled dependency" in msg
def test_install_deps_then_package(tmpdir, mock_fetch, install_mockery):
dep = Spec("dependency-install").concretized()
root = Spec("dependent-install").concretized()
install("--only", "dependencies", "dependent-install")
assert os.path.exists(dep.prefix)
assert not os.path.exists(root.prefix)
install("--only", "package", "dependent-install")
assert os.path.exists(root.prefix)
@pytest.mark.not_on_windows("Environment views not supported on windows. Revisit after #34701")
@pytest.mark.regression("12002")
def test_install_only_dependencies_in_env(
tmpdir, mock_fetch, install_mockery, mutable_mock_env_path
):
env("create", "test")
with ev.read("test"):
dep = Spec("dependency-install").concretized()
root = Spec("dependent-install").concretized()
install("-v", "--only", "dependencies", "--add", "dependent-install")
assert os.path.exists(dep.prefix)
assert not os.path.exists(root.prefix)
@pytest.mark.regression("12002")
def test_install_only_dependencies_of_all_in_env(
tmpdir, mock_fetch, install_mockery, mutable_mock_env_path
):
env("create", "--without-view", "test")
with ev.read("test"):
roots = [
Spec("dependent-install@1.0").concretized(),
Spec("dependent-install@2.0").concretized(),
]
add("dependent-install@1.0")
add("dependent-install@2.0")
install("--only", "dependencies")
for root in roots:
assert not os.path.exists(root.prefix)
for dep in root.traverse(root=False):
assert os.path.exists(dep.prefix)
def test_install_no_add_in_env(tmpdir, mock_fetch, install_mockery, mutable_mock_env_path):
# To test behavior of --add option, we create the following environment:
#
# mpileaks
# ^callpath
# ^dyninst
# ^libelf@0.8.13 # or latest, really
# ^libdwarf
# ^mpich
# libelf@0.8.10
# pkg-a~bvv
# ^pkg-b
# pkg-a
# ^pkg-b
e = ev.create("test", with_view=False)
e.add("mpileaks")
e.add("libelf@0.8.10") # so env has both root and dep libelf specs
e.add("pkg-a")
e.add("pkg-a ~bvv")
e.concretize()
e.write()
env_specs = e.all_specs()
a_spec = None
b_spec = None
mpi_spec = None
# First find and remember some target concrete specs in the environment
for e_spec in env_specs:
if e_spec.satisfies(Spec("pkg-a ~bvv")):
a_spec = e_spec
elif e_spec.name == "pkg-b":
b_spec = e_spec
elif e_spec.satisfies(Spec("mpi")):
mpi_spec = e_spec
assert a_spec
assert a_spec.concrete
assert b_spec
assert b_spec.concrete
assert b_spec not in e.roots()
assert mpi_spec
assert mpi_spec.concrete
# Activate the environment
with e:
# Assert using --no-add with a spec not in the env fails
inst_out = install("--fake", "--no-add", "boost", fail_on_error=False, output=str)
assert "You can add specs to the environment with 'spack add " in inst_out
# Without --add, ensure that two packages "a" get installed
inst_out = install("--fake", "pkg-a", output=str)
assert len([x for x in e.all_specs() if x.installed and x.name == "pkg-a"]) == 2
# Install an unambiguous dependency spec (that already exists as a dep
# in the environment) and make sure it gets installed (w/ deps),
# but is not added to the environment.
install("dyninst")
find_output = find("-l", output=str)
assert "dyninst" in find_output
assert "libdwarf" in find_output
assert "libelf" in find_output
assert "callpath" not in find_output
post_install_specs = e.all_specs()
assert all([s in env_specs for s in post_install_specs])
# Make sure we can install a concrete dependency spec from a spec.json
# file on disk, and the spec is installed but not added as a root
mpi_spec_json_path = tmpdir.join("{0}.json".format(mpi_spec.name))
with open(mpi_spec_json_path.strpath, "w", encoding="utf-8") as fd:
fd.write(mpi_spec.to_json(hash=ht.dag_hash))
install("-f", mpi_spec_json_path.strpath)
assert mpi_spec not in e.roots()
find_output = find("-l", output=str)
assert mpi_spec.name in find_output
# Install an unambiguous depependency spec (that already exists as a
# dep in the environment) with --add and make sure it is added as a
# root of the environment as well as installed.
assert b_spec not in e.roots()
install("--fake", "--add", "pkg-b")
assert b_spec in e.roots()
assert b_spec not in e.uninstalled_specs()
# Install a novel spec with --add and make sure it is added as a root
# and installed.
install("--fake", "--add", "bowtie")
assert any([s.name == "bowtie" for s in e.roots()])
assert not any([s.name == "bowtie" for s in e.uninstalled_specs()])
def test_install_help_does_not_show_cdash_options(capsys):
"""
Make sure `spack install --help` does not describe CDash arguments
"""
with pytest.raises(SystemExit):
install("--help")
captured = capsys.readouterr()
assert "CDash URL" not in captured.out
def test_install_help_cdash():
"""Make sure `spack install --help-cdash` describes CDash arguments"""
install_cmd = SpackCommand("install")
out = install_cmd("--help-cdash")
assert "CDash URL" in out
@pytest.mark.disable_clean_stage_check
def test_cdash_auth_token(tmpdir, mock_fetch, install_mockery, monkeypatch, capfd):
# capfd interferes with Spack's capturing
with tmpdir.as_cwd(), capfd.disabled():
monkeypatch.setenv("SPACK_CDASH_AUTH_TOKEN", "asdf")
out = install("--fake", "-v", "--log-file=cdash_reports", "--log-format=cdash", "pkg-a")
assert "Using CDash auth token from environment" in out
@pytest.mark.not_on_windows("Windows log_output logs phase header out of order")
@pytest.mark.disable_clean_stage_check
def test_cdash_configure_warning(tmpdir, mock_fetch, install_mockery, capfd):
# capfd interferes with Spack's capturing of e.g., Build.xml output
with capfd.disabled(), tmpdir.as_cwd():
# Test would fail if install raised an error.
# Ensure that even on non-x86_64 architectures, there are no
# dependencies installed
spec = Spec("configure-warning").concretized()
spec.clear_dependencies()
specfile = "./spec.json"
with open(specfile, "w", encoding="utf-8") as f:
f.write(spec.to_json())
print(spec.to_json())
install("--log-file=cdash_reports", "--log-format=cdash", specfile)
# Verify Configure.xml exists with expected contents.
report_dir = tmpdir.join("cdash_reports")
assert report_dir in tmpdir.listdir()
report_file = report_dir.join("Configure.xml")
assert report_file in report_dir.listdir()
content = report_file.open().read()
assert "foo: No such file or directory" in content
def test_install_fails_no_args(tmpdir):
# ensure no spack.yaml in directory
with tmpdir.as_cwd():
output = install(fail_on_error=False)
# check we got the short version of the error message with no spack.yaml
assert "requires a package argument or active environment" in output
assert "spack env activate ." not in output
assert "using the `spack.yaml` in this directory" not in output
def test_install_fails_no_args_suggests_env_activation(tmpdir):
# ensure spack.yaml in directory
tmpdir.ensure("spack.yaml")
with tmpdir.as_cwd():
output = install(fail_on_error=False)
# check we got the long version of the error message with spack.yaml
assert "requires a package argument or active environment" in output
assert "spack env activate ." in output
assert "using the `spack.yaml` in this directory" in output
@pytest.mark.not_on_windows("Environment views not supported on windows. Revisit after #34701")
def test_install_env_with_tests_all(
tmpdir, mock_packages, mock_fetch, install_mockery, mutable_mock_env_path
):
env("create", "test")
with ev.read("test"):
test_dep = Spec("test-dependency").concretized()
add("depb")
install("--fake", "--test", "all")
assert os.path.exists(test_dep.prefix)
@pytest.mark.not_on_windows("Environment views not supported on windows. Revisit after #34701")
def test_install_env_with_tests_root(
tmpdir, mock_packages, mock_fetch, install_mockery, mutable_mock_env_path
):
env("create", "test")
with ev.read("test"):
test_dep = Spec("test-dependency").concretized()
add("depb")
install("--fake", "--test", "root")
assert not os.path.exists(test_dep.prefix)
@pytest.mark.not_on_windows("Environment views not supported on windows. Revisit after #34701")
def test_install_empty_env(
tmpdir, mock_packages, mock_fetch, install_mockery, mutable_mock_env_path
):
env_name = "empty"
env("create", env_name)
with ev.read(env_name):
out = install(fail_on_error=False)
assert env_name in out
assert "environment" in out
assert "no specs to install" in out
@pytest.mark.not_on_windows("Windows logger I/O operation on closed file when install fails")
@pytest.mark.disable_clean_stage_check
@pytest.mark.parametrize(
"name,method",
[
("test-build-callbacks", "undefined-build-test"),
("test-install-callbacks", "undefined-install-test"),
],
)
def test_installation_fail_tests(install_mockery, mock_fetch, name, method):
"""Confirm build-time tests with unknown methods fail."""
output = install("--test=root", "--no-cache", name, fail_on_error=False)
# Check that there is a single test failure reported
assert output.count("TestFailure: 1 test failed") == 1
# Check that the method appears twice: no attribute error and in message
assert output.count(method) == 2
assert output.count("method not implemented") == 1
# Check that the path to the test log file is also output
assert "See test log for details" in output
@pytest.mark.not_on_windows("Buildcache not supported on windows")
def test_install_use_buildcache(
capsys, mock_packages, mock_fetch, mock_archive, mock_binary_index, tmpdir, install_mockery
):
"""
Make sure installing with use-buildcache behaves correctly.
"""
package_name = "dependent-install"
dependency_name = "dependency-install"
def validate(mode, out, pkg):
def assert_auto(pkg, out):
assert "==> Extracting {0}".format(pkg) in out
def assert_only(pkg, out):
assert "==> Extracting {0}".format(pkg) in out
def assert_never(pkg, out):
assert "==> {0}: Executing phase: 'install'".format(pkg) in out
if mode == "auto":
assert_auto(pkg, out)
elif mode == "only":
assert_only(pkg, out)
else:
assert_never(pkg, out)
def install_use_buildcache(opt):
out = install(
"--no-check-signature", "--use-buildcache", opt, package_name, fail_on_error=True
)
pkg_opt, dep_opt = spack.cmd.common.arguments.use_buildcache(opt)
validate(dep_opt, out, dependency_name)
validate(pkg_opt, out, package_name)
# Clean up installed packages
uninstall("-y", "-a")
# Setup the mirror
# Create a temp mirror directory for buildcache usage
mirror_dir = tmpdir.join("mirror_dir")
mirror_url = "file://{0}".format(mirror_dir.strpath)
# Populate the buildcache
install(package_name)
buildcache("push", "-u", "-f", mirror_dir.strpath, package_name, dependency_name)
# Uninstall the all of the packages for clean slate
uninstall("-y", "-a")
# Configure the mirror where we put that buildcache w/ the compiler
mirror("add", "test-mirror", mirror_url)
with capsys.disabled():
# Install using the matrix of possible combinations with --use-buildcache
for pkg, deps in itertools.product(["auto", "only", "never"], repeat=2):
tty.debug(
"Testing `spack install --use-buildcache package:{0},dependencies:{1}`".format(
pkg, deps
)
)
install_use_buildcache("package:{0},dependencies:{1}".format(pkg, deps))
install_use_buildcache("dependencies:{0},package:{1}".format(deps, pkg))
# Install using a default override option
# Alternative to --cache-only (always) or --no-cache (never)
for opt in ["auto", "only", "never"]:
install_use_buildcache(opt)
@pytest.mark.not_on_windows("Windows logger I/O operation on closed file when install fails")
@pytest.mark.regression("34006")
@pytest.mark.disable_clean_stage_check
def test_padded_install_runtests_root(install_mockery, mock_fetch):
spack.config.set("config:install_tree:padded_length", 255)
output = install("--test=root", "--no-cache", "test-build-callbacks", fail_on_error=False)
assert output.count("method not implemented") == 1
@pytest.mark.regression("35337")
def test_report_filename_for_cdash(install_mockery, mock_fetch):
"""Test that the temporary file used to write the XML for CDash is not the upload URL"""
parser = argparse.ArgumentParser()
spack.cmd.install.setup_parser(parser)
args = parser.parse_args(
["--cdash-upload-url", "https://blahblah/submit.php?project=debugging", "pkg-a"]
)
specs = spack.cmd.install.concrete_specs_from_cli(args, {})
filename = spack.cmd.install.report_filename(args, specs)
assert filename != "https://blahblah/submit.php?project=debugging"