1544 lines
52 KiB
Python
1544 lines
52 KiB
Python
# Copyright 2013-2024 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 collections
|
|
import getpass
|
|
import io
|
|
import os
|
|
import tempfile
|
|
from datetime import date
|
|
|
|
import pytest
|
|
|
|
import llnl.util.tty as tty
|
|
from llnl.util.filesystem import join_path, touch, touchp
|
|
|
|
import spack
|
|
import spack.config
|
|
import spack.directory_layout
|
|
import spack.environment as ev
|
|
import spack.error
|
|
import spack.package_base
|
|
import spack.paths
|
|
import spack.platforms
|
|
import spack.repo
|
|
import spack.schema.compilers
|
|
import spack.schema.config
|
|
import spack.schema.env
|
|
import spack.schema.mirrors
|
|
import spack.schema.repos
|
|
import spack.spec
|
|
import spack.store
|
|
import spack.util.path as spack_path
|
|
import spack.util.spack_yaml as syaml
|
|
|
|
# sample config data
|
|
config_low = {
|
|
"config": {
|
|
"install_tree": {"root": "install_tree_path"},
|
|
"build_stage": ["path1", "path2", "path3"],
|
|
}
|
|
}
|
|
|
|
config_override_all = {"config:": {"install_tree:": {"root": "override_all"}}}
|
|
|
|
config_override_key = {"config": {"install_tree:": {"root": "override_key"}}}
|
|
|
|
config_merge_list = {"config": {"build_stage": ["patha", "pathb"]}}
|
|
|
|
config_override_list = {"config": {"build_stage:": ["pathd", "pathe"]}}
|
|
|
|
config_merge_dict = {"config": {"info": {"a": 3, "b": 4}}}
|
|
|
|
config_override_dict = {"config": {"info:": {"a": 7, "c": 9}}}
|
|
|
|
|
|
@pytest.fixture()
|
|
def write_config_file(tmpdir):
|
|
"""Returns a function that writes a config file."""
|
|
|
|
def _write(config, data, scope):
|
|
config_yaml = tmpdir.join(scope, config + ".yaml")
|
|
config_yaml.ensure()
|
|
with config_yaml.open("w") as f:
|
|
syaml.dump_config(data, f)
|
|
|
|
return _write
|
|
|
|
|
|
@pytest.fixture()
|
|
def env_yaml(tmpdir):
|
|
"""Return a sample env.yaml for test purposes"""
|
|
env_yaml = str(tmpdir.join("env.yaml"))
|
|
with open(env_yaml, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
config:
|
|
verify_ssl: False
|
|
dirty: False
|
|
packages:
|
|
all:
|
|
compiler: [ 'gcc@4.5.3' ]
|
|
repos:
|
|
- /x/y/z
|
|
"""
|
|
)
|
|
return env_yaml
|
|
|
|
|
|
def cross_plat_join(*pths):
|
|
"""os.path.join does not prepend paths to other paths
|
|
beginning with a Windows drive label i.e. D:\\
|
|
"""
|
|
return os.sep.join([pth for pth in pths])
|
|
|
|
|
|
def check_compiler_config(comps, *compiler_names):
|
|
"""Check that named compilers in comps match Spack's config."""
|
|
config = spack.config.get("compilers")
|
|
compiler_list = ["cc", "cxx", "f77", "fc"]
|
|
flag_list = ["cflags", "cxxflags", "fflags", "cppflags", "ldflags", "ldlibs"]
|
|
param_list = ["modules", "paths", "spec", "operating_system"]
|
|
for compiler in config:
|
|
conf = compiler["compiler"]
|
|
if conf["spec"] in compiler_names:
|
|
comp = next(
|
|
(c["compiler"] for c in comps if c["compiler"]["spec"] == conf["spec"]), None
|
|
)
|
|
if not comp:
|
|
raise ValueError("Bad config spec")
|
|
for p in param_list:
|
|
assert conf[p] == comp[p]
|
|
for f in flag_list:
|
|
expected = comp.get("flags", {}).get(f, None)
|
|
actual = conf.get("flags", {}).get(f, None)
|
|
assert expected == actual
|
|
for c in compiler_list:
|
|
expected = comp["paths"][c]
|
|
actual = conf["paths"][c]
|
|
assert expected == actual
|
|
|
|
|
|
#
|
|
# Some sample compiler config data and tests.
|
|
#
|
|
a_comps = {
|
|
"compilers": [
|
|
{
|
|
"compiler": {
|
|
"paths": {"cc": "/gcc473", "cxx": "/g++473", "f77": None, "fc": None},
|
|
"modules": None,
|
|
"spec": "gcc@4.7.3",
|
|
"operating_system": "CNL10",
|
|
}
|
|
},
|
|
{
|
|
"compiler": {
|
|
"paths": {"cc": "/gcc450", "cxx": "/g++450", "f77": "gfortran", "fc": "gfortran"},
|
|
"modules": None,
|
|
"spec": "gcc@4.5.0",
|
|
"operating_system": "CNL10",
|
|
}
|
|
},
|
|
{
|
|
"compiler": {
|
|
"paths": {"cc": "/gcc422", "cxx": "/g++422", "f77": "gfortran", "fc": "gfortran"},
|
|
"flags": {"cppflags": "-O0 -fpic", "fflags": "-f77"},
|
|
"modules": None,
|
|
"spec": "gcc@4.2.2",
|
|
"operating_system": "CNL10",
|
|
}
|
|
},
|
|
{
|
|
"compiler": {
|
|
"paths": {
|
|
"cc": "<overwritten>",
|
|
"cxx": "<overwritten>",
|
|
"f77": "<overwritten>",
|
|
"fc": "<overwritten>",
|
|
},
|
|
"modules": None,
|
|
"spec": "clang@3.3",
|
|
"operating_system": "CNL10",
|
|
}
|
|
},
|
|
]
|
|
}
|
|
|
|
b_comps = {
|
|
"compilers": [
|
|
{
|
|
"compiler": {
|
|
"paths": {"cc": "/icc100", "cxx": "/icp100", "f77": None, "fc": None},
|
|
"modules": None,
|
|
"spec": "icc@10.0",
|
|
"operating_system": "CNL10",
|
|
}
|
|
},
|
|
{
|
|
"compiler": {
|
|
"paths": {"cc": "/icc111", "cxx": "/icp111", "f77": "ifort", "fc": "ifort"},
|
|
"modules": None,
|
|
"spec": "icc@11.1",
|
|
"operating_system": "CNL10",
|
|
}
|
|
},
|
|
{
|
|
"compiler": {
|
|
"paths": {"cc": "/icc123", "cxx": "/icp123", "f77": "ifort", "fc": "ifort"},
|
|
"flags": {"cppflags": "-O3", "fflags": "-f77rtl"},
|
|
"modules": None,
|
|
"spec": "icc@12.3",
|
|
"operating_system": "CNL10",
|
|
}
|
|
},
|
|
{
|
|
"compiler": {
|
|
"paths": {
|
|
"cc": "<overwritten>",
|
|
"cxx": "<overwritten>",
|
|
"f77": "<overwritten>",
|
|
"fc": "<overwritten>",
|
|
},
|
|
"modules": None,
|
|
"spec": "clang@3.3",
|
|
"operating_system": "CNL10",
|
|
}
|
|
},
|
|
]
|
|
}
|
|
|
|
|
|
@pytest.fixture()
|
|
def compiler_specs():
|
|
"""Returns a couple of compiler specs needed for the tests"""
|
|
a = [ac["compiler"]["spec"] for ac in a_comps["compilers"]]
|
|
b = [bc["compiler"]["spec"] for bc in b_comps["compilers"]]
|
|
CompilerSpecs = collections.namedtuple("CompilerSpecs", ["a", "b"])
|
|
return CompilerSpecs(a=a, b=b)
|
|
|
|
|
|
def test_write_key_in_memory(mock_low_high_config, compiler_specs):
|
|
# Write b_comps "on top of" a_comps.
|
|
spack.config.set("compilers", a_comps["compilers"], scope="low")
|
|
spack.config.set("compilers", b_comps["compilers"], scope="high")
|
|
|
|
# Make sure the config looks how we expect.
|
|
check_compiler_config(a_comps["compilers"], *compiler_specs.a)
|
|
check_compiler_config(b_comps["compilers"], *compiler_specs.b)
|
|
|
|
|
|
def test_write_key_to_disk(mock_low_high_config, compiler_specs):
|
|
# Write b_comps "on top of" a_comps.
|
|
spack.config.set("compilers", a_comps["compilers"], scope="low")
|
|
spack.config.set("compilers", b_comps["compilers"], scope="high")
|
|
|
|
# Clear caches so we're forced to read from disk.
|
|
spack.config.CONFIG.clear_caches()
|
|
|
|
# Same check again, to ensure consistency.
|
|
check_compiler_config(a_comps["compilers"], *compiler_specs.a)
|
|
check_compiler_config(b_comps["compilers"], *compiler_specs.b)
|
|
|
|
|
|
def test_write_to_same_priority_file(mock_low_high_config, compiler_specs):
|
|
# Write b_comps in the same file as a_comps.
|
|
spack.config.set("compilers", a_comps["compilers"], scope="low")
|
|
spack.config.set("compilers", b_comps["compilers"], scope="low")
|
|
|
|
# Clear caches so we're forced to read from disk.
|
|
spack.config.CONFIG.clear_caches()
|
|
|
|
# Same check again, to ensure consistency.
|
|
check_compiler_config(a_comps["compilers"], *compiler_specs.a)
|
|
check_compiler_config(b_comps["compilers"], *compiler_specs.b)
|
|
|
|
|
|
#
|
|
# Sample repo data and tests
|
|
#
|
|
repos_low = {"repos": ["/some/path"]}
|
|
repos_high = {"repos": ["/some/other/path"]}
|
|
|
|
# Test setting config values via path in filename
|
|
|
|
|
|
def test_add_config_path(mutable_config):
|
|
# Try setting a new install tree root
|
|
path = "config:install_tree:root:/path/to/config.yaml"
|
|
spack.config.add(path)
|
|
set_value = spack.config.get("config")["install_tree"]["root"]
|
|
assert set_value == "/path/to/config.yaml"
|
|
|
|
# Now a package:all setting
|
|
path = "packages:all:compiler:[gcc]"
|
|
spack.config.add(path)
|
|
compilers = spack.config.get("packages")["all"]["compiler"]
|
|
assert "gcc" in compilers
|
|
|
|
# Try quotes to escape brackets
|
|
path = "config:install_tree:projections:cmake:\
|
|
'{architecture}/{compiler.name}-{compiler.version}/{name}-{version}-{hash}'"
|
|
spack.config.add(path)
|
|
set_value = spack.config.get("config")["install_tree"]["projections"]["cmake"]
|
|
assert set_value == "{architecture}/{compiler.name}-{compiler.version}/{name}-{version}-{hash}"
|
|
|
|
path = 'modules:default:tcl:all:environment:set:"{name}_ROOT":"{prefix}"'
|
|
spack.config.add(path)
|
|
set_value = spack.config.get("modules")["default"]["tcl"]["all"]["environment"]["set"]
|
|
assert r"{name}_ROOT" in set_value
|
|
assert set_value[r"{name}_ROOT"] == r"{prefix}"
|
|
assert spack.config.get('modules:default:tcl:all:environment:set:"{name}_ROOT"') == r"{prefix}"
|
|
|
|
# NOTE:
|
|
# The config path: "config:install_tree:root:<path>" is unique in that it can accept multiple
|
|
# schemas (such as a dropped "root" component) which is atypical and may lead to passing tests
|
|
# when the behavior is in reality incorrect.
|
|
# the config path below is such that no subkey accepts a string as a valid entry in our schema
|
|
|
|
# try quotes to escape colons
|
|
path = "config:build_stage:'C:\\path\\to\\config.yaml'"
|
|
spack.config.add(path)
|
|
set_value = spack.config.get("config")["build_stage"]
|
|
assert "C:\\path\\to\\config.yaml" in set_value
|
|
|
|
|
|
@pytest.mark.regression("17543,23259")
|
|
def test_add_config_path_with_enumerated_type(mutable_config):
|
|
spack.config.add("config:flags:keep_werror:all")
|
|
assert spack.config.get("config")["flags"]["keep_werror"] == "all"
|
|
|
|
spack.config.add("config:flags:keep_werror:specific")
|
|
assert spack.config.get("config")["flags"]["keep_werror"] == "specific"
|
|
|
|
with pytest.raises(spack.error.ConfigError):
|
|
spack.config.add("config:flags:keep_werror:foo")
|
|
|
|
|
|
def test_add_config_filename(mock_low_high_config, tmpdir):
|
|
config_yaml = tmpdir.join("config-filename.yaml")
|
|
config_yaml.ensure()
|
|
with config_yaml.open("w") as f:
|
|
syaml.dump_config(config_low, f)
|
|
|
|
spack.config.add_from_file(str(config_yaml), scope="low")
|
|
assert "build_stage" in spack.config.get("config")
|
|
build_stages = spack.config.get("config")["build_stage"]
|
|
for stage in config_low["config"]["build_stage"]:
|
|
assert stage in build_stages
|
|
|
|
|
|
# repos
|
|
def test_write_list_in_memory(mock_low_high_config):
|
|
spack.config.set("repos", repos_low["repos"], scope="low")
|
|
spack.config.set("repos", repos_high["repos"], scope="high")
|
|
|
|
config = spack.config.get("repos")
|
|
assert config == repos_high["repos"] + repos_low["repos"]
|
|
|
|
|
|
class MockEnv:
|
|
def __init__(self, path):
|
|
self.path = path
|
|
|
|
|
|
def test_substitute_config_variables(mock_low_high_config, monkeypatch):
|
|
prefix = spack.paths.prefix.lstrip("/")
|
|
assert cross_plat_join(
|
|
os.sep + os.path.join("foo", "bar", "baz"), prefix
|
|
) == spack_path.canonicalize_path("/foo/bar/baz/$spack")
|
|
|
|
assert cross_plat_join(
|
|
spack.paths.prefix, os.path.join("foo", "bar", "baz")
|
|
) == spack_path.canonicalize_path("$spack/foo/bar/baz/")
|
|
|
|
assert cross_plat_join(
|
|
os.sep + os.path.join("foo", "bar", "baz"), prefix, os.path.join("foo", "bar", "baz")
|
|
) == spack_path.canonicalize_path("/foo/bar/baz/$spack/foo/bar/baz/")
|
|
|
|
assert cross_plat_join(
|
|
os.sep + os.path.join("foo", "bar", "baz"), prefix
|
|
) == spack_path.canonicalize_path("/foo/bar/baz/${spack}")
|
|
|
|
assert cross_plat_join(
|
|
spack.paths.prefix, os.path.join("foo", "bar", "baz")
|
|
) == spack_path.canonicalize_path("${spack}/foo/bar/baz/")
|
|
|
|
assert cross_plat_join(
|
|
os.sep + os.path.join("foo", "bar", "baz"), prefix, os.path.join("foo", "bar", "baz")
|
|
) == spack_path.canonicalize_path("/foo/bar/baz/${spack}/foo/bar/baz/")
|
|
|
|
assert cross_plat_join(
|
|
os.sep + os.path.join("foo", "bar", "baz"), prefix, os.path.join("foo", "bar", "baz")
|
|
) != spack_path.canonicalize_path("/foo/bar/baz/${spack/foo/bar/baz/")
|
|
|
|
# $env replacement is a no-op when no environment is active
|
|
assert spack_path.canonicalize_path(
|
|
os.sep + os.path.join("foo", "bar", "baz", "$env")
|
|
) == os.sep + os.path.join("foo", "bar", "baz", "$env")
|
|
|
|
# Fake an active environment and $env is replaced properly
|
|
fake_env_path = os.sep + os.path.join("quux", "quuux")
|
|
monkeypatch.setattr(ev, "active_environment", lambda: MockEnv(fake_env_path))
|
|
assert spack_path.canonicalize_path("$env/foo/bar/baz") == os.path.join(
|
|
fake_env_path, os.path.join("foo", "bar", "baz")
|
|
)
|
|
|
|
# relative paths without source information are relative to cwd
|
|
assert spack_path.canonicalize_path(os.path.join("foo", "bar", "baz")) == os.path.abspath(
|
|
os.path.join("foo", "bar", "baz")
|
|
)
|
|
|
|
# relative paths with source information are relative to the file
|
|
spack.config.set(
|
|
"modules:default", {"roots": {"lmod": os.path.join("foo", "bar", "baz")}}, scope="low"
|
|
)
|
|
spack.config.CONFIG.clear_caches()
|
|
path = spack.config.get("modules:default:roots:lmod")
|
|
assert spack_path.canonicalize_path(path) == os.path.normpath(
|
|
os.path.join(mock_low_high_config.scopes["low"].path, os.path.join("foo", "bar", "baz"))
|
|
)
|
|
|
|
# test architecture information is in replacements
|
|
assert spack_path.canonicalize_path(
|
|
os.path.join("foo", "$platform", "bar")
|
|
) == os.path.abspath(os.path.join("foo", "test", "bar"))
|
|
|
|
host_target = spack.platforms.host().target("default_target")
|
|
host_target_family = str(host_target.family)
|
|
assert spack_path.canonicalize_path(
|
|
os.path.join("foo", "$target_family", "bar")
|
|
) == os.path.abspath(os.path.join("foo", host_target_family, "bar"))
|
|
|
|
|
|
packages_merge_low = {"packages": {"foo": {"variants": ["+v1"]}, "bar": {"variants": ["+v2"]}}}
|
|
|
|
packages_merge_high = {
|
|
"packages": {
|
|
"foo": {"version": ["a"]},
|
|
"bar": {"version": ["b"], "variants": ["+v3"]},
|
|
"baz": {"version": ["c"]},
|
|
}
|
|
}
|
|
|
|
|
|
@pytest.mark.regression("7924")
|
|
def test_merge_with_defaults(mock_low_high_config, write_config_file):
|
|
"""This ensures that specified preferences merge with defaults as
|
|
expected. Originally all defaults were initialized with the
|
|
exact same object, which led to aliasing problems. Therefore
|
|
the test configs used here leave 'version' blank for multiple
|
|
packages in 'packages_merge_low'.
|
|
"""
|
|
write_config_file("packages", packages_merge_low, "low")
|
|
write_config_file("packages", packages_merge_high, "high")
|
|
cfg = spack.config.get("packages")
|
|
|
|
assert cfg["foo"]["version"] == ["a"]
|
|
assert cfg["bar"]["version"] == ["b"]
|
|
assert cfg["baz"]["version"] == ["c"]
|
|
|
|
|
|
def test_substitute_user(mock_low_high_config):
|
|
user = getpass.getuser()
|
|
assert os.sep + os.path.join(
|
|
"foo", "bar"
|
|
) + os.sep + user + os.sep + "baz" == spack_path.canonicalize_path(
|
|
os.sep + os.path.join("foo", "bar", "$user", "baz")
|
|
)
|
|
|
|
|
|
def test_substitute_user_cache(mock_low_high_config):
|
|
user_cache_path = spack.paths.user_cache_path
|
|
assert user_cache_path + os.sep + "baz" == spack_path.canonicalize_path(
|
|
os.path.join("$user_cache_path", "baz")
|
|
)
|
|
|
|
|
|
def test_substitute_tempdir(mock_low_high_config):
|
|
tempdir = tempfile.gettempdir()
|
|
assert tempdir == spack_path.canonicalize_path("$tempdir")
|
|
assert tempdir + os.sep + os.path.join("foo", "bar", "baz") == spack_path.canonicalize_path(
|
|
os.path.join("$tempdir", "foo", "bar", "baz")
|
|
)
|
|
|
|
|
|
def test_substitute_date(mock_low_high_config):
|
|
test_path = os.path.join("hello", "world", "on", "$date")
|
|
new_path = spack_path.canonicalize_path(test_path)
|
|
assert "$date" in test_path
|
|
assert date.today().strftime("%Y-%m-%d") in new_path
|
|
|
|
|
|
def test_substitute_spack_version():
|
|
version = spack.spack_version_info
|
|
assert spack_path.canonicalize_path(
|
|
"spack$spack_short_version/test"
|
|
) == spack_path.canonicalize_path(f"spack{version[0]}.{version[1]}/test")
|
|
|
|
|
|
PAD_STRING = spack_path.SPACK_PATH_PADDING_CHARS
|
|
MAX_PATH_LEN = spack_path.get_system_path_max()
|
|
MAX_PADDED_LEN = MAX_PATH_LEN - spack_path.SPACK_MAX_INSTALL_PATH_LENGTH
|
|
reps = [PAD_STRING for _ in range((MAX_PADDED_LEN // len(PAD_STRING) + 1) + 2)]
|
|
full_padded_string = os.path.join(os.sep + "path", os.sep.join(reps))[:MAX_PADDED_LEN]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"config_settings,expected",
|
|
[
|
|
([], [None, None, None]),
|
|
([["config:install_tree:root", os.sep + "path"]], [os.sep + "path", None, None]),
|
|
([["config:install_tree", os.sep + "path"]], [os.sep + "path", None, None]),
|
|
(
|
|
[["config:install_tree:projections", {"all": "{name}"}]],
|
|
[None, None, {"all": "{name}"}],
|
|
),
|
|
([["config:install_path_scheme", "{name}"]], [None, None, {"all": "{name}"}]),
|
|
],
|
|
)
|
|
def test_parse_install_tree(config_settings, expected, mutable_config):
|
|
expected_root = expected[0] or mutable_config.get("config:install_tree:root")
|
|
expected_unpadded_root = expected[1] or expected_root
|
|
expected_proj = expected[2] or spack.directory_layout.default_projections
|
|
|
|
# config settings is a list of 2-element lists, [path, value]
|
|
# where path is a config path and value is the value to set at that path
|
|
# these can be "splatted" in as the arguments to config.set
|
|
for config_setting in config_settings:
|
|
mutable_config.set(*config_setting)
|
|
|
|
config_dict = mutable_config.get("config")
|
|
root, unpadded_root, projections = spack.store.parse_install_tree(config_dict)
|
|
assert root == expected_root
|
|
assert unpadded_root == expected_unpadded_root
|
|
assert projections == expected_proj
|
|
|
|
|
|
def test_change_or_add(mutable_config, mock_packages):
|
|
spack.config.add("packages:a:version:['1.0']", scope="user")
|
|
|
|
spack.config.add("packages:b:version:['1.1']", scope="system")
|
|
|
|
class ChangeTest:
|
|
def __init__(self, pkg_name, new_version):
|
|
self.pkg_name = pkg_name
|
|
self.new_version = new_version
|
|
|
|
def find_fn(self, section):
|
|
return self.pkg_name in section
|
|
|
|
def change_fn(self, section):
|
|
pkg_section = section.get(self.pkg_name, {})
|
|
pkg_section["version"] = self.new_version
|
|
section[self.pkg_name] = pkg_section
|
|
|
|
change1 = ChangeTest("b", ["1.2"])
|
|
spack.config.change_or_add("packages", change1.find_fn, change1.change_fn)
|
|
assert "b" not in mutable_config.get("packages", scope="user")
|
|
assert mutable_config.get("packages")["b"]["version"] == ["1.2"]
|
|
|
|
change2 = ChangeTest("c", ["1.0"])
|
|
spack.config.change_or_add("packages", change2.find_fn, change2.change_fn)
|
|
assert "c" in mutable_config.get("packages", scope="user")
|
|
|
|
|
|
@pytest.mark.not_on_windows("Padding unsupported on Windows")
|
|
@pytest.mark.parametrize(
|
|
"config_settings,expected",
|
|
[
|
|
(
|
|
[
|
|
["config:install_tree:root", os.sep + "path"],
|
|
["config:install_tree:padded_length", 11],
|
|
],
|
|
[os.path.join(os.sep + "path", PAD_STRING[:5]), os.sep + "path", None],
|
|
),
|
|
(
|
|
[["config:install_tree:root", "/path/$padding:11"]],
|
|
[os.path.join(os.sep + "path", PAD_STRING[:5]), os.sep + "path", None],
|
|
),
|
|
(
|
|
[["config:install_tree", "/path/${padding:11}"]],
|
|
[os.path.join(os.sep + "path", PAD_STRING[:5]), os.sep + "path", None],
|
|
),
|
|
([["config:install_tree:padded_length", False]], [None, None, None]),
|
|
(
|
|
[
|
|
["config:install_tree:padded_length", True],
|
|
["config:install_tree:root", os.sep + "path"],
|
|
],
|
|
[full_padded_string, os.sep + "path", None],
|
|
),
|
|
(
|
|
[["config:install_tree:", os.sep + "path$padding"]],
|
|
[full_padded_string, os.sep + "path", None],
|
|
),
|
|
(
|
|
[["config:install_tree:", os.sep + "path" + os.sep + "${padding}"]],
|
|
[full_padded_string, os.sep + "path", None],
|
|
),
|
|
],
|
|
)
|
|
def test_parse_install_tree_padded(config_settings, expected, mutable_config):
|
|
expected_root = expected[0] or mutable_config.get("config:install_tree:root")
|
|
expected_unpadded_root = expected[1] or expected_root
|
|
expected_proj = expected[2] or spack.directory_layout.default_projections
|
|
|
|
# config settings is a list of 2-element lists, [path, value]
|
|
# where path is a config path and value is the value to set at that path
|
|
# these can be "splatted" in as the arguments to config.set
|
|
for config_setting in config_settings:
|
|
mutable_config.set(*config_setting)
|
|
|
|
config_dict = mutable_config.get("config")
|
|
root, unpadded_root, projections = spack.store.parse_install_tree(config_dict)
|
|
assert root == expected_root
|
|
assert unpadded_root == expected_unpadded_root
|
|
assert projections == expected_proj
|
|
|
|
|
|
def test_read_config(mock_low_high_config, write_config_file):
|
|
write_config_file("config", config_low, "low")
|
|
assert spack.config.get("config") == config_low["config"]
|
|
|
|
|
|
def test_read_config_override_all(mock_low_high_config, write_config_file):
|
|
write_config_file("config", config_low, "low")
|
|
write_config_file("config", config_override_all, "high")
|
|
assert spack.config.get("config") == {"install_tree": {"root": "override_all"}}
|
|
|
|
|
|
def test_read_config_override_key(mock_low_high_config, write_config_file):
|
|
write_config_file("config", config_low, "low")
|
|
write_config_file("config", config_override_key, "high")
|
|
assert spack.config.get("config") == {
|
|
"install_tree": {"root": "override_key"},
|
|
"build_stage": ["path1", "path2", "path3"],
|
|
}
|
|
|
|
|
|
def test_read_config_merge_list(mock_low_high_config, write_config_file):
|
|
write_config_file("config", config_low, "low")
|
|
write_config_file("config", config_merge_list, "high")
|
|
assert spack.config.get("config") == {
|
|
"install_tree": {"root": "install_tree_path"},
|
|
"build_stage": ["patha", "pathb", "path1", "path2", "path3"],
|
|
}
|
|
|
|
|
|
def test_read_config_override_list(mock_low_high_config, write_config_file):
|
|
write_config_file("config", config_low, "low")
|
|
write_config_file("config", config_override_list, "high")
|
|
assert spack.config.get("config") == {
|
|
"install_tree": {"root": "install_tree_path"},
|
|
"build_stage": config_override_list["config"]["build_stage:"],
|
|
}
|
|
|
|
|
|
def test_ordereddict_merge_order():
|
|
""" "Test that source keys come before dest keys in merge_yaml results."""
|
|
source = syaml.syaml_dict([("k1", "v1"), ("k2", "v2"), ("k3", "v3")])
|
|
|
|
dest = syaml.syaml_dict([("k4", "v4"), ("k3", "WRONG"), ("k5", "v5")])
|
|
|
|
result = spack.config.merge_yaml(dest, source)
|
|
assert "WRONG" not in result.values()
|
|
|
|
expected_keys = ["k1", "k2", "k3", "k4", "k5"]
|
|
expected_items = [("k1", "v1"), ("k2", "v2"), ("k3", "v3"), ("k4", "v4"), ("k5", "v5")]
|
|
assert expected_keys == list(result.keys())
|
|
assert expected_items == list(result.items())
|
|
|
|
|
|
def test_list_merge_order():
|
|
""" "Test that source lists are prepended to dest."""
|
|
source = ["a", "b", "c"]
|
|
dest = ["d", "e", "f"]
|
|
|
|
result = spack.config.merge_yaml(dest, source)
|
|
|
|
assert ["a", "b", "c", "d", "e", "f"] == result
|
|
|
|
|
|
def test_internal_config_update(mock_low_high_config, write_config_file):
|
|
write_config_file("config", config_low, "low")
|
|
|
|
before = mock_low_high_config.get("config")
|
|
assert before["install_tree"]["root"] == "install_tree_path"
|
|
|
|
# add an internal configuration scope
|
|
scope = spack.config.InternalConfigScope("command_line")
|
|
assert "InternalConfigScope" in repr(scope)
|
|
|
|
mock_low_high_config.push_scope(scope)
|
|
|
|
command_config = mock_low_high_config.get("config", scope="command_line")
|
|
command_config["install_tree"] = {"root": "foo/bar"}
|
|
|
|
mock_low_high_config.set("config", command_config, scope="command_line")
|
|
|
|
after = mock_low_high_config.get("config")
|
|
assert after["install_tree"]["root"] == "foo/bar"
|
|
|
|
|
|
def test_internal_config_filename(mock_low_high_config, write_config_file):
|
|
write_config_file("config", config_low, "low")
|
|
mock_low_high_config.push_scope(spack.config.InternalConfigScope("command_line"))
|
|
|
|
with pytest.raises(NotImplementedError):
|
|
mock_low_high_config.get_config_filename("command_line", "config")
|
|
|
|
|
|
def test_mark_internal():
|
|
data = {
|
|
"config": {
|
|
"bool": False,
|
|
"int": 6,
|
|
"numbers": [1, 2, 3],
|
|
"string": "foo",
|
|
"dict": {"more_numbers": [1, 2, 3], "another_string": "foo", "another_int": 7},
|
|
}
|
|
}
|
|
|
|
marked = spack.config._mark_internal(data, "x")
|
|
|
|
# marked version should be equal to the original
|
|
assert data == marked
|
|
|
|
def assert_marked(obj):
|
|
if type(obj) is bool:
|
|
return # can't subclass bool, so can't mark it
|
|
|
|
assert hasattr(obj, "_start_mark") and obj._start_mark.name == "x"
|
|
assert hasattr(obj, "_end_mark") and obj._end_mark.name == "x"
|
|
|
|
# everything in the marked version should have marks
|
|
checks = (
|
|
marked.keys(),
|
|
marked.values(),
|
|
marked["config"].keys(),
|
|
marked["config"].values(),
|
|
marked["config"]["numbers"],
|
|
marked["config"]["dict"].keys(),
|
|
marked["config"]["dict"].values(),
|
|
marked["config"]["dict"]["more_numbers"],
|
|
)
|
|
|
|
for seq in checks:
|
|
for obj in seq:
|
|
assert_marked(obj)
|
|
|
|
|
|
def test_internal_config_from_data():
|
|
config = spack.config.Configuration()
|
|
|
|
# add an internal config initialized from an inline dict
|
|
config.push_scope(
|
|
spack.config.InternalConfigScope(
|
|
"_builtin", {"config": {"verify_ssl": False, "build_jobs": 6}}
|
|
)
|
|
)
|
|
|
|
assert config.get("config:verify_ssl", scope="_builtin") is False
|
|
assert config.get("config:build_jobs", scope="_builtin") == 6
|
|
|
|
assert config.get("config:verify_ssl") is False
|
|
assert config.get("config:build_jobs") == 6
|
|
|
|
# push one on top and see what happens.
|
|
config.push_scope(
|
|
spack.config.InternalConfigScope(
|
|
"higher", {"config": {"checksum": True, "verify_ssl": True}}
|
|
)
|
|
)
|
|
|
|
assert config.get("config:verify_ssl", scope="_builtin") is False
|
|
assert config.get("config:build_jobs", scope="_builtin") == 6
|
|
|
|
assert config.get("config:verify_ssl", scope="higher") is True
|
|
assert config.get("config:build_jobs", scope="higher") is None
|
|
|
|
assert config.get("config:verify_ssl") is True
|
|
assert config.get("config:build_jobs") == 6
|
|
assert config.get("config:checksum") is True
|
|
|
|
assert config.get("config:checksum", scope="_builtin") is None
|
|
assert config.get("config:checksum", scope="higher") is True
|
|
|
|
|
|
def test_keys_are_ordered(configuration_dir):
|
|
"""Test that keys in Spack YAML files retain their order from the file."""
|
|
expected_order = (
|
|
"./bin",
|
|
"./man",
|
|
"./share/man",
|
|
"./share/aclocal",
|
|
"./lib/pkgconfig",
|
|
"./lib64/pkgconfig",
|
|
"./share/pkgconfig",
|
|
"./",
|
|
)
|
|
|
|
config_scope = spack.config.DirectoryConfigScope("modules", configuration_dir.join("site"))
|
|
|
|
data = config_scope.get_section("modules")
|
|
|
|
prefix_inspections = data["modules"]["prefix_inspections"]
|
|
|
|
for actual, expected in zip(prefix_inspections, expected_order):
|
|
assert actual == expected
|
|
|
|
|
|
def test_config_format_error(mutable_config):
|
|
"""This is raised when we try to write a bad configuration."""
|
|
with pytest.raises(spack.config.ConfigFormatError):
|
|
spack.config.set("compilers", {"bad": "data"}, scope="site")
|
|
|
|
|
|
def get_config_error(filename, schema, yaml_string):
|
|
"""Parse a YAML string and return the resulting ConfigFormatError.
|
|
|
|
Fail if there is no ConfigFormatError
|
|
"""
|
|
with open(filename, "w") as f:
|
|
f.write(yaml_string)
|
|
|
|
# parse and return error, or fail.
|
|
try:
|
|
spack.config.read_config_file(filename, schema)
|
|
except spack.config.ConfigFormatError as e:
|
|
return e
|
|
else:
|
|
pytest.fail("ConfigFormatError was not raised!")
|
|
|
|
|
|
def test_config_parse_dict_in_list(tmpdir):
|
|
with tmpdir.as_cwd():
|
|
e = get_config_error(
|
|
"repos.yaml",
|
|
spack.schema.repos.schema,
|
|
"""\
|
|
repos:
|
|
- https://foobar.com/foo
|
|
- https://foobar.com/bar
|
|
- error:
|
|
- abcdef
|
|
- https://foobar.com/baz
|
|
""",
|
|
)
|
|
assert "repos.yaml:4" in str(e)
|
|
|
|
|
|
def test_config_parse_str_not_bool(tmpdir):
|
|
with tmpdir.as_cwd():
|
|
e = get_config_error(
|
|
"config.yaml",
|
|
spack.schema.config.schema,
|
|
"""\
|
|
config:
|
|
verify_ssl: False
|
|
checksum: foobar
|
|
dirty: True
|
|
""",
|
|
)
|
|
assert "config.yaml:3" in str(e)
|
|
|
|
|
|
def test_config_parse_list_in_dict(tmpdir):
|
|
with tmpdir.as_cwd():
|
|
e = get_config_error(
|
|
"mirrors.yaml",
|
|
spack.schema.mirrors.schema,
|
|
"""\
|
|
mirrors:
|
|
foo: http://foobar.com/baz
|
|
bar: http://barbaz.com/foo
|
|
baz: http://bazfoo.com/bar
|
|
travis: [1, 2, 3]
|
|
""",
|
|
)
|
|
assert "mirrors.yaml:5" in str(e)
|
|
|
|
|
|
def test_bad_config_section(mock_low_high_config):
|
|
"""Test that getting or setting a bad section gives an error."""
|
|
with pytest.raises(spack.config.ConfigSectionError):
|
|
spack.config.set("foobar", "foobar")
|
|
|
|
with pytest.raises(spack.config.ConfigSectionError):
|
|
spack.config.get("foobar")
|
|
|
|
|
|
def test_bad_command_line_scopes(tmp_path, config):
|
|
cfg = spack.config.Configuration()
|
|
file_path = tmp_path / "file_instead_of_dir"
|
|
non_existing_path = tmp_path / "non_existing_dir"
|
|
|
|
file_path.write_text("")
|
|
|
|
with pytest.raises(spack.error.ConfigError):
|
|
spack.config._add_command_line_scopes(cfg, [str(file_path)])
|
|
|
|
with pytest.raises(spack.error.ConfigError):
|
|
spack.config._add_command_line_scopes(cfg, [str(non_existing_path)])
|
|
|
|
|
|
def test_add_command_line_scopes(tmpdir, mutable_config):
|
|
config_yaml = str(tmpdir.join("config.yaml"))
|
|
with open(config_yaml, "w") as f:
|
|
f.write(
|
|
"""\
|
|
config:
|
|
verify_ssl: False
|
|
dirty: False
|
|
"""
|
|
)
|
|
|
|
spack.config._add_command_line_scopes(mutable_config, [str(tmpdir)])
|
|
assert mutable_config.get("config:verify_ssl") is False
|
|
assert mutable_config.get("config:dirty") is False
|
|
|
|
|
|
def test_add_command_line_scope_env(tmp_path, mutable_mock_env_path):
|
|
"""Test whether --config-scope <env> works, either by name or path."""
|
|
managed_env = ev.create("example").manifest_path
|
|
|
|
with open(managed_env, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
config:
|
|
install_tree:
|
|
root: /tmp/first
|
|
"""
|
|
)
|
|
|
|
with open(tmp_path / "spack.yaml", "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
config:
|
|
install_tree:
|
|
root: /tmp/second
|
|
"""
|
|
)
|
|
|
|
config = spack.config.Configuration()
|
|
spack.config._add_command_line_scopes(config, ["example", str(tmp_path)])
|
|
assert len(config.scopes) == 2
|
|
assert config.get("config:install_tree:root") == "/tmp/second"
|
|
|
|
config = spack.config.Configuration()
|
|
spack.config._add_command_line_scopes(config, [str(tmp_path), "example"])
|
|
assert len(config.scopes) == 2
|
|
assert config.get("config:install_tree:root") == "/tmp/first"
|
|
|
|
assert ev.active_environment() is None # shouldn't cause an environment to be activated
|
|
|
|
|
|
def test_nested_override():
|
|
"""Ensure proper scope naming of nested overrides."""
|
|
base_name = spack.config._OVERRIDES_BASE_NAME
|
|
|
|
def _check_scopes(num_expected, debug_values):
|
|
scope_names = [
|
|
s.name for s in spack.config.CONFIG.scopes.values() if s.name.startswith(base_name)
|
|
]
|
|
|
|
for i in range(num_expected):
|
|
name = "{0}{1}".format(base_name, i)
|
|
assert name in scope_names
|
|
|
|
data = spack.config.CONFIG.get_config("config", name)
|
|
assert data["debug"] == debug_values[i]
|
|
|
|
# Check results from single and nested override
|
|
with spack.config.override("config:debug", True):
|
|
with spack.config.override("config:debug", False):
|
|
_check_scopes(2, [True, False])
|
|
|
|
_check_scopes(1, [True])
|
|
|
|
|
|
def test_alternate_override(monkeypatch):
|
|
"""Ensure proper scope naming of override when conflict present."""
|
|
base_name = spack.config._OVERRIDES_BASE_NAME
|
|
|
|
def _matching_scopes(regexpr):
|
|
return [spack.config.InternalConfigScope("{0}1".format(base_name))]
|
|
|
|
# Check that the alternate naming works
|
|
monkeypatch.setattr(spack.config.CONFIG, "matching_scopes", _matching_scopes)
|
|
|
|
with spack.config.override("config:debug", False):
|
|
name = "{0}2".format(base_name)
|
|
|
|
scope_names = [
|
|
s.name for s in spack.config.CONFIG.scopes.values() if s.name.startswith(base_name)
|
|
]
|
|
assert name in scope_names
|
|
|
|
data = spack.config.CONFIG.get_config("config", name)
|
|
assert data["debug"] is False
|
|
|
|
|
|
def test_immutable_scope(tmpdir):
|
|
config_yaml = str(tmpdir.join("config.yaml"))
|
|
with open(config_yaml, "w") as f:
|
|
f.write(
|
|
"""\
|
|
config:
|
|
install_tree:
|
|
root: dummy_tree_value
|
|
"""
|
|
)
|
|
scope = spack.config.DirectoryConfigScope("test", str(tmpdir), writable=False)
|
|
|
|
data = scope.get_section("config")
|
|
assert data["config"]["install_tree"] == {"root": "dummy_tree_value"}
|
|
|
|
with pytest.raises(spack.error.ConfigError):
|
|
scope._write_section("config")
|
|
|
|
|
|
def test_single_file_scope(config, env_yaml):
|
|
scope = spack.config.SingleFileScope(
|
|
"env", env_yaml, spack.schema.env.schema, yaml_path=["spack"]
|
|
)
|
|
|
|
with spack.config.override(scope):
|
|
# from the single-file config
|
|
assert spack.config.get("config:verify_ssl") is False
|
|
assert spack.config.get("config:dirty") is False
|
|
assert spack.config.get("packages:all:compiler") == ["gcc@4.5.3", "gcc", "clang"]
|
|
|
|
# from the lower config scopes
|
|
assert spack.config.get("config:checksum") is True
|
|
assert spack.config.get("config:checksum") is True
|
|
assert spack.config.get("packages:externalmodule:buildable") is False
|
|
assert spack.config.get("repos") == ["/x/y/z", "$spack/var/spack/repos/builtin"]
|
|
|
|
|
|
def test_single_file_scope_section_override(tmpdir, config):
|
|
"""Check that individual config sections can be overridden in an
|
|
environment config. The config here primarily differs in that the
|
|
``packages`` section is intended to override all other scopes (using the
|
|
"::" syntax).
|
|
"""
|
|
env_yaml = str(tmpdir.join("env.yaml"))
|
|
with open(env_yaml, "w") as f:
|
|
f.write(
|
|
"""\
|
|
spack:
|
|
config:
|
|
verify_ssl: False
|
|
packages::
|
|
all:
|
|
compiler: [ 'gcc@4.5.3' ]
|
|
repos:
|
|
- /x/y/z
|
|
"""
|
|
)
|
|
|
|
scope = spack.config.SingleFileScope(
|
|
"env", env_yaml, spack.schema.env.schema, yaml_path=["spack"]
|
|
)
|
|
|
|
with spack.config.override(scope):
|
|
# from the single-file config
|
|
assert spack.config.get("config:verify_ssl") is False
|
|
assert spack.config.get("packages:all:compiler") == ["gcc@4.5.3"]
|
|
|
|
# from the lower config scopes
|
|
assert spack.config.get("config:checksum") is True
|
|
assert not spack.config.get("packages:externalmodule")
|
|
assert spack.config.get("repos") == ["/x/y/z", "$spack/var/spack/repos/builtin"]
|
|
|
|
|
|
def test_write_empty_single_file_scope(tmpdir):
|
|
env_schema = spack.schema.env.schema
|
|
scope = spack.config.SingleFileScope(
|
|
"test", str(tmpdir.ensure("config.yaml")), env_schema, yaml_path=["spack"]
|
|
)
|
|
scope._write_section("config")
|
|
# confirm we can write empty config
|
|
assert not scope.get_section("config")
|
|
|
|
|
|
def check_schema(name, file_contents):
|
|
"""Check a Spack YAML schema against some data"""
|
|
f = io.StringIO(file_contents)
|
|
data = syaml.load_config(f)
|
|
spack.config.validate(data, name)
|
|
|
|
|
|
def test_good_env_yaml(tmpdir):
|
|
check_schema(
|
|
spack.schema.env.schema,
|
|
"""\
|
|
spack:
|
|
config:
|
|
verify_ssl: False
|
|
dirty: False
|
|
repos:
|
|
- ~/my/repo/location
|
|
mirrors:
|
|
remote: /foo/bar/baz
|
|
compilers:
|
|
- compiler:
|
|
spec: cce@2.1
|
|
operating_system: cnl
|
|
modules: []
|
|
paths:
|
|
cc: /path/to/cc
|
|
cxx: /path/to/cxx
|
|
fc: /path/to/fc
|
|
f77: /path/to/f77
|
|
""",
|
|
)
|
|
|
|
|
|
def test_bad_env_yaml(tmpdir):
|
|
with pytest.raises(spack.config.ConfigFormatError):
|
|
check_schema(
|
|
spack.schema.env.schema,
|
|
"""\
|
|
spack:
|
|
foobar:
|
|
verify_ssl: False
|
|
dirty: False
|
|
""",
|
|
)
|
|
|
|
|
|
def test_bad_config_yaml(tmpdir):
|
|
with pytest.raises(spack.config.ConfigFormatError):
|
|
check_schema(
|
|
spack.schema.config.schema,
|
|
"""\
|
|
config:
|
|
verify_ssl: False
|
|
install_tree:
|
|
root:
|
|
extra_level: foo
|
|
""",
|
|
)
|
|
|
|
|
|
def test_bad_mirrors_yaml(tmpdir):
|
|
with pytest.raises(spack.config.ConfigFormatError):
|
|
check_schema(
|
|
spack.schema.mirrors.schema,
|
|
"""\
|
|
mirrors:
|
|
local: True
|
|
""",
|
|
)
|
|
|
|
|
|
def test_bad_repos_yaml(tmpdir):
|
|
with pytest.raises(spack.config.ConfigFormatError):
|
|
check_schema(
|
|
spack.schema.repos.schema,
|
|
"""\
|
|
repos:
|
|
True
|
|
""",
|
|
)
|
|
|
|
|
|
def test_bad_compilers_yaml(tmpdir):
|
|
with pytest.raises(spack.config.ConfigFormatError):
|
|
check_schema(
|
|
spack.schema.compilers.schema,
|
|
"""\
|
|
compilers:
|
|
key_instead_of_list: 'value'
|
|
""",
|
|
)
|
|
|
|
with pytest.raises(spack.config.ConfigFormatError):
|
|
check_schema(
|
|
spack.schema.compilers.schema,
|
|
"""\
|
|
compilers:
|
|
- shmompiler:
|
|
environment: /bad/value
|
|
""",
|
|
)
|
|
|
|
with pytest.raises(spack.config.ConfigFormatError):
|
|
check_schema(
|
|
spack.schema.compilers.schema,
|
|
"""\
|
|
compilers:
|
|
- compiler:
|
|
fenfironfent: /bad/value
|
|
""",
|
|
)
|
|
|
|
|
|
def test_internal_config_section_override(mock_low_high_config, write_config_file):
|
|
write_config_file("config", config_merge_list, "low")
|
|
wanted_list = config_override_list["config"]["build_stage:"]
|
|
mock_low_high_config.push_scope(
|
|
spack.config.InternalConfigScope("high", {"config:": {"build_stage": wanted_list}})
|
|
)
|
|
assert mock_low_high_config.get("config:build_stage") == wanted_list
|
|
|
|
|
|
def test_internal_config_dict_override(mock_low_high_config, write_config_file):
|
|
write_config_file("config", config_merge_dict, "low")
|
|
wanted_dict = config_override_dict["config"]["info:"]
|
|
mock_low_high_config.push_scope(spack.config.InternalConfigScope("high", config_override_dict))
|
|
assert mock_low_high_config.get("config:info") == wanted_dict
|
|
|
|
|
|
def test_internal_config_list_override(mock_low_high_config, write_config_file):
|
|
write_config_file("config", config_merge_list, "low")
|
|
wanted_list = config_override_list["config"]["build_stage:"]
|
|
mock_low_high_config.push_scope(spack.config.InternalConfigScope("high", config_override_list))
|
|
assert mock_low_high_config.get("config:build_stage") == wanted_list
|
|
|
|
|
|
def test_set_section_override(mock_low_high_config, write_config_file):
|
|
write_config_file("config", config_merge_list, "low")
|
|
wanted_list = config_override_list["config"]["build_stage:"]
|
|
|
|
# Ensure test validity:
|
|
assert wanted_list != mock_low_high_config.get("config:build_stage")
|
|
|
|
# Test both bare section with full value and section override in path
|
|
with spack.config.override("config::build_stage", wanted_list):
|
|
assert mock_low_high_config.get("config:build_stage") == wanted_list
|
|
with spack.config.override("config::", {"build_stage": wanted_list}):
|
|
assert mock_low_high_config.get("config:build_stage") == wanted_list
|
|
|
|
assert config_merge_list["config"]["build_stage"] == mock_low_high_config.get(
|
|
"config:build_stage"
|
|
)
|
|
|
|
|
|
def test_set_list_override(mock_low_high_config, write_config_file):
|
|
write_config_file("config", config_merge_list, "low")
|
|
wanted_list = config_override_list["config"]["build_stage:"]
|
|
with spack.config.override("config:build_stage:", wanted_list):
|
|
assert wanted_list == mock_low_high_config.get("config:build_stage")
|
|
assert config_merge_list["config"]["build_stage"] == mock_low_high_config.get(
|
|
"config:build_stage"
|
|
)
|
|
|
|
|
|
def test_set_dict_override(mock_low_high_config, write_config_file):
|
|
write_config_file("config", config_merge_dict, "low")
|
|
wanted_dict = config_override_dict["config"]["info:"]
|
|
with spack.config.override("config:info:", wanted_dict):
|
|
assert wanted_dict == mock_low_high_config.get("config:info")
|
|
assert config_merge_dict["config"]["info"] == mock_low_high_config.get("config:info")
|
|
|
|
|
|
def test_set_bad_path(config):
|
|
with pytest.raises(ValueError):
|
|
with spack.config.override(":bad:path", ""):
|
|
pass
|
|
|
|
|
|
def test_bad_path_double_override(config):
|
|
with pytest.raises(syaml.SpackYAMLError, match="Meaningless second override"):
|
|
with spack.config.override("bad::double:override::directive", ""):
|
|
pass
|
|
|
|
|
|
def test_license_dir_config(mutable_config, mock_packages):
|
|
"""Ensure license directory is customizable"""
|
|
expected_dir = spack.paths.default_license_dir
|
|
assert spack.config.get("config:license_dir") == expected_dir
|
|
assert spack.package_base.PackageBase.global_license_dir == expected_dir
|
|
assert spack.repo.PATH.get_pkg_class("pkg-a").global_license_dir == expected_dir
|
|
|
|
rel_path = os.path.join(os.path.sep, "foo", "bar", "baz")
|
|
spack.config.set("config:license_dir", rel_path)
|
|
assert spack.config.get("config:license_dir") == rel_path
|
|
assert spack.package_base.PackageBase.global_license_dir == rel_path
|
|
assert spack.repo.PATH.get_pkg_class("pkg-a").global_license_dir == rel_path
|
|
|
|
|
|
@pytest.mark.regression("22547")
|
|
def test_single_file_scope_cache_clearing(env_yaml):
|
|
scope = spack.config.SingleFileScope(
|
|
"env", env_yaml, spack.schema.env.schema, yaml_path=["spack"]
|
|
)
|
|
# Check that we can retrieve data from the single file scope
|
|
before = scope.get_section("config")
|
|
assert before
|
|
# Clear the cache of the Single file scope
|
|
scope.clear()
|
|
# Check that the section can be retireved again and it's
|
|
# the same as before
|
|
after = scope.get_section("config")
|
|
assert after
|
|
assert before == after
|
|
|
|
|
|
@pytest.mark.regression("22611")
|
|
def test_internal_config_scope_cache_clearing():
|
|
"""
|
|
An InternalConfigScope object is constructed from data that is already
|
|
in memory, therefore it doesn't have any cache to clear. Here we ensure
|
|
that calling the clear method is consistent with that..
|
|
"""
|
|
data = {"config": {"build_jobs": 10}}
|
|
internal_scope = spack.config.InternalConfigScope("internal", data)
|
|
# Ensure that the initial object is properly set
|
|
assert internal_scope.sections["config"] == data
|
|
# Call the clear method
|
|
internal_scope.clear()
|
|
# Check that this didn't affect the scope object
|
|
assert internal_scope.sections["config"] == data
|
|
|
|
|
|
def test_system_config_path_is_overridable(working_env):
|
|
p = "/some/path"
|
|
os.environ["SPACK_SYSTEM_CONFIG_PATH"] = p
|
|
assert spack.paths._get_system_config_path() == p
|
|
|
|
|
|
def test_system_config_path_is_default_when_env_var_is_empty(working_env):
|
|
os.environ["SPACK_SYSTEM_CONFIG_PATH"] = ""
|
|
assert os.sep + os.path.join("etc", "spack") == spack.paths._get_system_config_path()
|
|
|
|
|
|
def test_user_config_path_is_overridable(working_env):
|
|
p = "/some/path"
|
|
os.environ["SPACK_USER_CONFIG_PATH"] = p
|
|
assert p == spack.paths._get_user_config_path()
|
|
|
|
|
|
def test_user_config_path_is_default_when_env_var_is_empty(working_env):
|
|
os.environ["SPACK_USER_CONFIG_PATH"] = ""
|
|
assert os.path.expanduser("~%s.spack" % os.sep) == spack.paths._get_user_config_path()
|
|
|
|
|
|
def test_default_install_tree(monkeypatch, default_config):
|
|
s = spack.spec.Spec("nonexistent@x.y.z %none@a.b.c arch=foo-bar-baz")
|
|
monkeypatch.setattr(s, "dag_hash", lambda length: "abc123")
|
|
_, _, projections = spack.store.parse_install_tree(spack.config.get("config"))
|
|
assert s.format(projections["all"]) == "foo-bar-baz/none-a.b.c/nonexistent-x.y.z-abc123"
|
|
|
|
|
|
def test_local_config_can_be_disabled(working_env):
|
|
os.environ["SPACK_DISABLE_LOCAL_CONFIG"] = "true"
|
|
cfg = spack.config.create()
|
|
assert "defaults" in cfg.scopes
|
|
assert "system" not in cfg.scopes
|
|
assert "site" in cfg.scopes
|
|
assert "user" not in cfg.scopes
|
|
|
|
os.environ["SPACK_DISABLE_LOCAL_CONFIG"] = ""
|
|
cfg = spack.config.create()
|
|
assert "defaults" in cfg.scopes
|
|
assert "system" not in cfg.scopes
|
|
assert "site" in cfg.scopes
|
|
assert "user" not in cfg.scopes
|
|
|
|
del os.environ["SPACK_DISABLE_LOCAL_CONFIG"]
|
|
cfg = spack.config.create()
|
|
assert "defaults" in cfg.scopes
|
|
assert "system" in cfg.scopes
|
|
assert "site" in cfg.scopes
|
|
assert "user" in cfg.scopes
|
|
|
|
|
|
def test_user_cache_path_is_overridable(working_env):
|
|
p = "/some/path"
|
|
os.environ["SPACK_USER_CACHE_PATH"] = p
|
|
assert spack.paths._get_user_cache_path() == p
|
|
|
|
|
|
def test_user_cache_path_is_default_when_env_var_is_empty(working_env):
|
|
os.environ["SPACK_USER_CACHE_PATH"] = ""
|
|
assert os.path.expanduser("~%s.spack" % os.sep) == spack.paths._get_user_cache_path()
|
|
|
|
|
|
github_url = "https://github.com/fake/fake/{0}/develop"
|
|
gitlab_url = "https://gitlab.fake.io/user/repo/-/blob/config/defaults"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"url,isfile",
|
|
[
|
|
(github_url.format("tree"), False),
|
|
("{0}/README.md".format(github_url.format("blob")), True),
|
|
("{0}/etc/fake/defaults/packages.yaml".format(github_url.format("blob")), True),
|
|
(gitlab_url, False),
|
|
(None, False),
|
|
],
|
|
)
|
|
def test_config_collect_urls(mutable_empty_config, mock_spider_configs, url, isfile):
|
|
with spack.config.override("config:url_fetch_method", "curl"):
|
|
urls = spack.config.collect_urls(url)
|
|
if url:
|
|
if isfile:
|
|
expected = 1 if url.endswith(".yaml") else 0
|
|
assert len(urls) == expected
|
|
else:
|
|
# Expect multiple configuration files for a "directory"
|
|
assert len(urls) > 1
|
|
else:
|
|
assert not urls
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"url,isfile,fail",
|
|
[
|
|
(github_url.format("tree"), False, False),
|
|
(gitlab_url, False, False),
|
|
("{0}/README.md".format(github_url.format("blob")), True, True),
|
|
("{0}/compilers.yaml".format(gitlab_url), True, False),
|
|
(None, False, True),
|
|
],
|
|
)
|
|
def test_config_fetch_remote_configs(
|
|
tmpdir, mutable_empty_config, mock_collect_urls, mock_curl_configs, url, isfile, fail
|
|
):
|
|
def _has_content(filename):
|
|
# The first element of all configuration files for this test happen to
|
|
# be the basename of the file so this check leverages that feature. If
|
|
# that changes, then this check will need to change accordingly.
|
|
element = "{0}:".format(os.path.splitext(os.path.basename(filename))[0])
|
|
with open(filename, "r") as fd:
|
|
for line in fd:
|
|
if element in line:
|
|
return True
|
|
tty.debug("Expected {0} in '{1}'".format(element, filename))
|
|
return False
|
|
|
|
dest_dir = join_path(tmpdir.strpath, "defaults")
|
|
if fail:
|
|
msg = "Cannot retrieve configuration"
|
|
with spack.config.override("config:url_fetch_method", "curl"):
|
|
with pytest.raises(spack.config.ConfigFileError, match=msg):
|
|
spack.config.fetch_remote_configs(url, dest_dir)
|
|
else:
|
|
with spack.config.override("config:url_fetch_method", "curl"):
|
|
path = spack.config.fetch_remote_configs(url, dest_dir)
|
|
assert os.path.exists(path)
|
|
if isfile:
|
|
# Ensure correct file is "fetched"
|
|
assert os.path.basename(path) == os.path.basename(url)
|
|
# Ensure contents of the file has expected config element
|
|
assert _has_content(path)
|
|
else:
|
|
for filename in os.listdir(path):
|
|
assert _has_content(join_path(path, filename))
|
|
|
|
|
|
@pytest.fixture(scope="function")
|
|
def mock_collect_urls(mock_config_data, monkeypatch):
|
|
"""Mock the collection of URLs to avoid mocking spider."""
|
|
|
|
_, config_files = mock_config_data
|
|
|
|
def _collect(base_url):
|
|
if not base_url:
|
|
return []
|
|
|
|
ext = os.path.splitext(base_url)[1]
|
|
if ext:
|
|
return [base_url] if ext == ".yaml" else []
|
|
|
|
return [join_path(base_url, f) for f in config_files]
|
|
|
|
monkeypatch.setattr(spack.config, "collect_urls", _collect)
|
|
|
|
yield
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"url,skip",
|
|
[(github_url.format("tree"), True), ("{0}/compilers.yaml".format(gitlab_url), True)],
|
|
)
|
|
def test_config_fetch_remote_configs_skip(
|
|
tmpdir, mutable_empty_config, mock_collect_urls, mock_curl_configs, url, skip
|
|
):
|
|
"""Ensure skip fetching remote config file if it already exists when
|
|
required and not skipping if replacing it."""
|
|
|
|
def check_contents(filename, expected):
|
|
with open(filename, "r") as fd:
|
|
lines = fd.readlines()
|
|
if expected:
|
|
assert lines[0] == "compilers:"
|
|
else:
|
|
assert not lines
|
|
|
|
dest_dir = join_path(tmpdir.strpath, "defaults")
|
|
filename = "compilers.yaml"
|
|
|
|
# Create a stage directory with an empty configuration file
|
|
path = join_path(dest_dir, filename)
|
|
touchp(path)
|
|
|
|
# Do NOT replace the existing cached configuration file if skipping
|
|
expected = None if skip else "compilers:"
|
|
|
|
with spack.config.override("config:url_fetch_method", "curl"):
|
|
path = spack.config.fetch_remote_configs(url, dest_dir, skip)
|
|
result_filename = path if path.endswith(".yaml") else join_path(path, filename)
|
|
check_contents(result_filename, expected)
|
|
|
|
|
|
def test_config_file_dir_failure(tmpdir, mutable_empty_config):
|
|
with pytest.raises(spack.config.ConfigFileError, match="not a file"):
|
|
spack.config.read_config_file(tmpdir.strpath)
|
|
|
|
|
|
@pytest.mark.not_on_windows("chmod not supported on Windows")
|
|
def test_config_file_read_perms_failure(tmpdir, mutable_empty_config):
|
|
"""Test reading a configuration file without permissions to ensure
|
|
ConfigFileError is raised."""
|
|
filename = join_path(tmpdir.strpath, "test.yaml")
|
|
touch(filename)
|
|
os.chmod(filename, 0o200)
|
|
|
|
with pytest.raises(spack.config.ConfigFileError, match="not readable"):
|
|
spack.config.read_config_file(filename)
|
|
|
|
|
|
def test_config_file_read_invalid_yaml(tmpdir, mutable_empty_config):
|
|
"""Test reading a configuration file with invalid (unparseable) YAML
|
|
raises a ConfigFileError."""
|
|
filename = join_path(tmpdir.strpath, "test.yaml")
|
|
with open(filename, "w") as f:
|
|
f.write("spack:\nview")
|
|
|
|
with pytest.raises(spack.config.ConfigFileError, match="parsing YAML"):
|
|
spack.config.read_config_file(filename)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"path,it_should_work,expected_parsed",
|
|
[
|
|
("x:y:z", True, ["x:", "y:", "z"]),
|
|
("x+::y:z", True, ["x+::", "y:", "z"]),
|
|
('x:y:"{z}"', True, ["x:", "y:", '"{z}"']),
|
|
('x:"y"+:z', True, ["x:", '"y"+:', "z"]),
|
|
('x:"y"trail:z', False, None),
|
|
("x:y:[1.0]", True, ["x:", "y:", "[1.0]"]),
|
|
("x:y:['1.0']", True, ["x:", "y:", "['1.0']"]),
|
|
("x:{y}:z", True, ["x:", "{y}:", "z"]),
|
|
("x:'{y}':z", True, ["x:", "'{y}':", "z"]),
|
|
("x:{y}", True, ["x:", "{y}"]),
|
|
],
|
|
)
|
|
def test_config_path_dsl(path, it_should_work, expected_parsed):
|
|
if it_should_work:
|
|
assert spack.config.ConfigPath._validate(path) == expected_parsed
|
|
else:
|
|
with pytest.raises(ValueError):
|
|
spack.config.ConfigPath._validate(path)
|