unit-test: fix reading Cray manifest files
This commit is contained in:
parent
28d42eed5e
commit
eb85f2e862
@ -27,6 +27,13 @@
|
|||||||
import spack.store
|
import spack.store
|
||||||
from spack.cray_manifest import compiler_from_entry, entries_to_specs
|
from spack.cray_manifest import compiler_from_entry, entries_to_specs
|
||||||
|
|
||||||
|
pytestmark = [
|
||||||
|
pytest.mark.skipif(
|
||||||
|
str(spack.platforms.host()) != "linux", reason="Cray manifest files are only for linux"
|
||||||
|
),
|
||||||
|
pytest.mark.usefixtures("mutable_config", "mock_packages"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class JsonSpecEntry:
|
class JsonSpecEntry:
|
||||||
def __init__(self, name, hash, prefix, version, arch, compiler, dependencies, parameters):
|
def __init__(self, name, hash, prefix, version, arch, compiler, dependencies, parameters):
|
||||||
@ -69,27 +76,24 @@ def compiler_json(self):
|
|||||||
|
|
||||||
|
|
||||||
class JsonCompilerEntry:
|
class JsonCompilerEntry:
|
||||||
def __init__(self, name, version, arch=None, executables=None):
|
def __init__(self, *, name, version, arch=None, executables=None, prefix=None):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.version = version
|
self.version = version
|
||||||
if not arch:
|
self.arch = arch or JsonArchEntry("anyplatform", "anyos", "anytarget")
|
||||||
arch = JsonArchEntry("anyplatform", "anyos", "anytarget")
|
self.executables = executables or {"cc": "cc", "cxx": "cxx", "fc": "fc"}
|
||||||
if not executables:
|
self.prefix = prefix
|
||||||
executables = {
|
|
||||||
"cc": "/path/to/compiler/cc",
|
|
||||||
"cxx": "/path/to/compiler/cxx",
|
|
||||||
"fc": "/path/to/compiler/fc",
|
|
||||||
}
|
|
||||||
self.arch = arch
|
|
||||||
self.executables = executables
|
|
||||||
|
|
||||||
def compiler_json(self):
|
def compiler_json(self):
|
||||||
return {
|
result = {
|
||||||
"name": self.name,
|
"name": self.name,
|
||||||
"version": self.version,
|
"version": self.version,
|
||||||
"arch": self.arch.compiler_json(),
|
"arch": self.arch.compiler_json(),
|
||||||
"executables": self.executables,
|
"executables": self.executables,
|
||||||
}
|
}
|
||||||
|
# See https://github.com/spack/spack/pull/40061
|
||||||
|
if self.prefix is not None:
|
||||||
|
result["prefix"] = self.prefix
|
||||||
|
return result
|
||||||
|
|
||||||
def spec_json(self):
|
def spec_json(self):
|
||||||
"""The compiler spec only lists the name/version, not
|
"""The compiler spec only lists the name/version, not
|
||||||
@ -178,30 +182,27 @@ def test_manifest_compatibility(_common_arch, _common_compiler, _raw_json_x):
|
|||||||
assert x_from_entry == _raw_json_x
|
assert x_from_entry == _raw_json_x
|
||||||
|
|
||||||
|
|
||||||
def test_compiler_from_entry():
|
def test_compiler_from_entry(mock_executable):
|
||||||
compiler_data = json.loads(
|
"""Tests that we can detect a compiler from a valid entry in the Cray manifest"""
|
||||||
"""\
|
cc = mock_executable("gcc", output="echo 7.5.0")
|
||||||
{
|
cxx = mock_executable("g++", output="echo 7.5.0")
|
||||||
"name": "gcc",
|
fc = mock_executable("gfortran", output="echo 7.5.0")
|
||||||
"prefix": "/path/to/compiler/",
|
|
||||||
"version": "7.5.0",
|
compiler = compiler_from_entry(
|
||||||
"arch": {
|
JsonCompilerEntry(
|
||||||
"os": "centos8",
|
name="gcc",
|
||||||
"target": "x86_64"
|
version="7.5.0",
|
||||||
},
|
arch=JsonArchEntry(platform="linux", os="centos8", target="x86_64"),
|
||||||
"executables": {
|
prefix=str(cc.parent),
|
||||||
"cc": "/path/to/compiler/cc",
|
executables={"cc": "gcc", "cxx": "g++", "fc": "gfortran"},
|
||||||
"cxx": "/path/to/compiler/cxx",
|
).compiler_json(),
|
||||||
"fc": "/path/to/compiler/fc"
|
manifest_path="/example/file",
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
compiler = compiler_from_entry(compiler_data, "/example/file")
|
|
||||||
assert compiler.cc == "/path/to/compiler/cc"
|
assert compiler.satisfies("gcc@7.5.0 target=x86_64 os=centos8")
|
||||||
assert compiler.cxx == "/path/to/compiler/cxx"
|
assert compiler.extra_attributes["compilers"]["c"] == str(cc)
|
||||||
assert compiler.fc == "/path/to/compiler/fc"
|
assert compiler.extra_attributes["compilers"]["cxx"] == str(cxx)
|
||||||
assert compiler.operating_system == "centos8"
|
assert compiler.extra_attributes["compilers"]["fortran"] == str(fc)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@ -262,7 +263,7 @@ def the_host_is_linux():
|
|||||||
|
|
||||||
cray_arch = JsonArchEntry(platform="cray", os="rhel8", target="x86_64")
|
cray_arch = JsonArchEntry(platform="cray", os="rhel8", target="x86_64")
|
||||||
spec_json = JsonSpecEntry(
|
spec_json = JsonSpecEntry(
|
||||||
name="cray-mpich",
|
name="mpich",
|
||||||
hash="craympichfakehashaaa",
|
hash="craympichfakehashaaa",
|
||||||
prefix="/path/to/cray-mpich/",
|
prefix="/path/to/cray-mpich/",
|
||||||
version="1.0.0",
|
version="1.0.0",
|
||||||
@ -276,30 +277,12 @@ def the_host_is_linux():
|
|||||||
assert spec.architecture.platform == "linux"
|
assert spec.architecture.platform == "linux"
|
||||||
|
|
||||||
|
|
||||||
def test_translate_compiler_name(_common_arch):
|
@pytest.mark.parametrize(
|
||||||
nvidia_compiler = JsonCompilerEntry(
|
"name_in_manifest,expected_name",
|
||||||
name="nvidia",
|
[("nvidia", "nvhpc"), ("rocm", "llvm-amdgpu"), ("clang", "llvm")],
|
||||||
version="19.1",
|
)
|
||||||
arch=_common_arch,
|
def test_translated_compiler_name(name_in_manifest, expected_name):
|
||||||
executables={"cc": "/path/to/compiler/nvc", "cxx": "/path/to/compiler/nvc++"},
|
assert cray_manifest.translated_compiler_name(name_in_manifest) == expected_name
|
||||||
)
|
|
||||||
|
|
||||||
compiler = compiler_from_entry(nvidia_compiler.compiler_json(), "/example/file")
|
|
||||||
assert compiler.name == "nvhpc"
|
|
||||||
|
|
||||||
spec_json = JsonSpecEntry(
|
|
||||||
name="hwloc",
|
|
||||||
hash="hwlocfakehashaaa",
|
|
||||||
prefix="/path/to/hwloc-install/",
|
|
||||||
version="2.0.3",
|
|
||||||
arch=_common_arch.spec_json(),
|
|
||||||
compiler=nvidia_compiler.spec_json(),
|
|
||||||
dependencies={},
|
|
||||||
parameters={},
|
|
||||||
).to_dict()
|
|
||||||
|
|
||||||
(spec,) = entries_to_specs([spec_json]).values()
|
|
||||||
assert spec.compiler.name == "nvhpc"
|
|
||||||
|
|
||||||
|
|
||||||
def test_failed_translate_compiler_name(_common_arch):
|
def test_failed_translate_compiler_name(_common_arch):
|
||||||
@ -326,11 +309,9 @@ def test_failed_translate_compiler_name(_common_arch):
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def manifest_content(generate_openmpi_entries, _common_compiler, _other_compiler):
|
def manifest_content(generate_openmpi_entries, _common_compiler, _other_compiler):
|
||||||
return {
|
return {
|
||||||
# Note: the cray_manifest module doesn't use the _meta section right
|
|
||||||
# now, but it is anticipated to be useful
|
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"file-type": "cray-pe-json",
|
"file-type": "cray-pe-json",
|
||||||
"system-type": "test",
|
"system-type": "EX",
|
||||||
"schema-version": "1.3",
|
"schema-version": "1.3",
|
||||||
"cpe-version": "22.06",
|
"cpe-version": "22.06",
|
||||||
},
|
},
|
||||||
@ -339,86 +320,60 @@ def manifest_content(generate_openmpi_entries, _common_compiler, _other_compiler
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_read_cray_manifest(
|
def test_read_cray_manifest(temporary_store, manifest_file):
|
||||||
tmpdir, mutable_config, mock_packages, mutable_database, manifest_content
|
|
||||||
):
|
|
||||||
"""Check that (a) we can read the cray manifest and add it to the Spack
|
"""Check that (a) we can read the cray manifest and add it to the Spack
|
||||||
Database and (b) we can concretize specs based on that.
|
Database and (b) we can concretize specs based on that.
|
||||||
"""
|
"""
|
||||||
with tmpdir.as_cwd():
|
cray_manifest.read(str(manifest_file), True)
|
||||||
test_db_fname = "external-db.json"
|
|
||||||
with open(test_db_fname, "w", encoding="utf-8") as db_file:
|
|
||||||
json.dump(manifest_content, db_file)
|
|
||||||
cray_manifest.read(test_db_fname, True)
|
|
||||||
query_specs = spack.store.STORE.db.query("openmpi")
|
|
||||||
assert any(x.dag_hash() == "openmpifakehasha" for x in query_specs)
|
|
||||||
|
|
||||||
concretized_specs = spack.cmd.parse_specs(
|
query_specs = temporary_store.db.query("openmpi")
|
||||||
"depends-on-openmpi ^/openmpifakehasha".split(), concretize=True
|
assert any(x.dag_hash() == "openmpifakehasha" for x in query_specs)
|
||||||
)
|
|
||||||
assert concretized_specs[0]["hwloc"].dag_hash() == "hwlocfakehashaaa"
|
concretized_spec = spack.spec.Spec("depends-on-openmpi ^/openmpifakehasha").concretized()
|
||||||
|
assert concretized_spec["hwloc"].dag_hash() == "hwlocfakehashaaa"
|
||||||
|
|
||||||
|
|
||||||
def test_read_cray_manifest_add_compiler_failure(
|
def test_read_cray_manifest_add_compiler_failure(temporary_store, manifest_file, monkeypatch):
|
||||||
tmpdir, mutable_config, mock_packages, mutable_database, manifest_content, monkeypatch
|
"""Tests the Cray manifest can be read even if some compilers cannot be added."""
|
||||||
|
|
||||||
|
def _mock(entry, *, manifest_path):
|
||||||
|
if entry["name"] == "clang":
|
||||||
|
raise RuntimeError("cannot determine the compiler")
|
||||||
|
return spack.spec.Spec(f"{entry['name']}@{entry['version']}")
|
||||||
|
|
||||||
|
monkeypatch.setattr(cray_manifest, "compiler_from_entry", _mock)
|
||||||
|
|
||||||
|
cray_manifest.read(str(manifest_file), True)
|
||||||
|
query_specs = spack.store.STORE.db.query("openmpi")
|
||||||
|
assert any(x.dag_hash() == "openmpifakehasha" for x in query_specs)
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_cray_manifest_twice_no_duplicates(
|
||||||
|
mutable_config, temporary_store, manifest_file, monkeypatch, tmp_path
|
||||||
):
|
):
|
||||||
"""Check that cray manifest can be read even if some compilers cannot
|
def _mock(entry, *, manifest_path):
|
||||||
be added.
|
return spack.spec.Spec(f"{entry['name']}@{entry['version']}", external_path=str(tmp_path))
|
||||||
"""
|
|
||||||
orig_add_compiler_to_config = spack.compilers.config.add_compiler_to_config
|
|
||||||
|
|
||||||
class fail_for_clang:
|
monkeypatch.setattr(cray_manifest, "compiler_from_entry", _mock)
|
||||||
def __init__(self):
|
|
||||||
self.called_with_clang = False
|
|
||||||
|
|
||||||
def __call__(self, compiler, **kwargs):
|
# Read the manifest twice
|
||||||
if compiler.name == "clang":
|
cray_manifest.read(str(manifest_file), True)
|
||||||
self.called_with_clang = True
|
cray_manifest.read(str(manifest_file), True)
|
||||||
raise Exception()
|
|
||||||
return orig_add_compiler_to_config(compiler, **kwargs)
|
|
||||||
|
|
||||||
checker = fail_for_clang()
|
config_data = mutable_config.get("packages")["gcc"]
|
||||||
monkeypatch.setattr(spack.compilers.config, "add_compiler_to_config", checker)
|
assert "externals" in config_data
|
||||||
|
|
||||||
with tmpdir.as_cwd():
|
specs = [spack.spec.Spec(x["spec"]) for x in config_data["externals"]]
|
||||||
test_db_fname = "external-db.json"
|
assert len(specs) == len(set(specs))
|
||||||
with open(test_db_fname, "w", encoding="utf-8") as db_file:
|
assert len([c for c in specs if c.satisfies("gcc@10.2.0.2112")]) == 1
|
||||||
json.dump(manifest_content, db_file)
|
|
||||||
cray_manifest.read(test_db_fname, True)
|
|
||||||
query_specs = spack.store.STORE.db.query("openmpi")
|
|
||||||
assert any(x.dag_hash() == "openmpifakehasha" for x in query_specs)
|
|
||||||
|
|
||||||
assert checker.called_with_clang
|
|
||||||
|
|
||||||
|
|
||||||
def test_read_cray_manifest_twice_no_compiler_duplicates(
|
def test_read_old_manifest_v1_2(tmp_path, temporary_store):
|
||||||
tmpdir, mutable_config, mock_packages, mutable_database, manifest_content
|
"""Test reading a file using the older format ('version' instead of 'schema-version')."""
|
||||||
):
|
manifest = tmp_path / "manifest_dir" / "test.json"
|
||||||
with tmpdir.as_cwd():
|
manifest.parent.mkdir(parents=True)
|
||||||
test_db_fname = "external-db.json"
|
manifest.write_text(
|
||||||
with open(test_db_fname, "w", encoding="utf-8") as db_file:
|
"""\
|
||||||
json.dump(manifest_content, db_file)
|
|
||||||
|
|
||||||
# Read the manifest twice
|
|
||||||
cray_manifest.read(test_db_fname, True)
|
|
||||||
cray_manifest.read(test_db_fname, True)
|
|
||||||
|
|
||||||
compilers = spack.compilers.config.all_compilers()
|
|
||||||
filtered = list(
|
|
||||||
c for c in compilers if c.spec == spack.spec.CompilerSpec("gcc@=10.2.0.2112")
|
|
||||||
)
|
|
||||||
assert len(filtered) == 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_read_old_manifest_v1_2(tmpdir, mutable_config, mock_packages, mutable_database):
|
|
||||||
"""Test reading a file using the older format
|
|
||||||
('version' instead of 'schema-version').
|
|
||||||
"""
|
|
||||||
manifest_dir = str(tmpdir.mkdir("manifest_dir"))
|
|
||||||
manifest_file_path = os.path.join(manifest_dir, "test.json")
|
|
||||||
with open(manifest_file_path, "w", encoding="utf-8") as manifest_file:
|
|
||||||
manifest_file.write(
|
|
||||||
"""\
|
|
||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"file-type": "cray-pe-json",
|
"file-type": "cray-pe-json",
|
||||||
@ -428,11 +383,11 @@ def test_read_old_manifest_v1_2(tmpdir, mutable_config, mock_packages, mutable_d
|
|||||||
"specs": []
|
"specs": []
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
cray_manifest.read(manifest_file_path, True)
|
cray_manifest.read(str(manifest), True)
|
||||||
|
|
||||||
|
|
||||||
def test_convert_validation_error(tmpdir, mutable_config, mock_packages, mutable_database):
|
def test_convert_validation_error(tmpdir, mutable_config, mock_packages, temporary_store):
|
||||||
manifest_dir = str(tmpdir.mkdir("manifest_dir"))
|
manifest_dir = str(tmpdir.mkdir("manifest_dir"))
|
||||||
# Does not parse as valid JSON
|
# Does not parse as valid JSON
|
||||||
invalid_json_path = os.path.join(manifest_dir, "invalid-json.json")
|
invalid_json_path = os.path.join(manifest_dir, "invalid-json.json")
|
||||||
@ -464,48 +419,40 @@ def test_convert_validation_error(tmpdir, mutable_config, mock_packages, mutable
|
|||||||
)
|
)
|
||||||
with pytest.raises(cray_manifest.ManifestValidationError) as e:
|
with pytest.raises(cray_manifest.ManifestValidationError) as e:
|
||||||
cray_manifest.read(invalid_schema_path, True)
|
cray_manifest.read(invalid_schema_path, True)
|
||||||
str(e)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def directory_with_manifest(tmpdir, manifest_content):
|
def manifest_file(tmp_path, manifest_content):
|
||||||
"""Create a manifest file in a directory. Used by 'spack external'."""
|
"""Create a manifest file in a directory. Used by 'spack external'."""
|
||||||
with tmpdir.as_cwd():
|
filename = tmp_path / "external-db.json"
|
||||||
test_db_fname = "external-db.json"
|
with open(filename, "w", encoding="utf-8") as db_file:
|
||||||
with open(test_db_fname, "w", encoding="utf-8") as db_file:
|
json.dump(manifest_content, db_file)
|
||||||
json.dump(manifest_content, db_file)
|
return filename
|
||||||
|
|
||||||
yield str(tmpdir)
|
|
||||||
|
|
||||||
|
|
||||||
def test_find_external_nonempty_default_manifest_dir(
|
def test_find_external_nonempty_default_manifest_dir(
|
||||||
mutable_database, mutable_mock_repo, tmpdir, monkeypatch, directory_with_manifest
|
temporary_store, mutable_mock_repo, tmpdir, monkeypatch, manifest_file
|
||||||
):
|
):
|
||||||
"""The user runs 'spack external find'; the default manifest directory
|
"""The user runs 'spack external find'; the default manifest directory
|
||||||
contains a manifest file. Ensure that the specs are read.
|
contains a manifest file. Ensure that the specs are read.
|
||||||
"""
|
"""
|
||||||
monkeypatch.setenv("PATH", "")
|
monkeypatch.setenv("PATH", "")
|
||||||
monkeypatch.setattr(spack.cray_manifest, "default_path", str(directory_with_manifest))
|
monkeypatch.setattr(spack.cray_manifest, "default_path", str(manifest_file.parent))
|
||||||
spack.cmd.external._collect_and_consume_cray_manifest_files(ignore_default_dir=False)
|
spack.cmd.external._collect_and_consume_cray_manifest_files(ignore_default_dir=False)
|
||||||
specs = spack.store.STORE.db.query("hwloc")
|
specs = temporary_store.db.query("hwloc")
|
||||||
assert any(x.dag_hash() == "hwlocfakehashaaa" for x in specs)
|
assert any(x.dag_hash() == "hwlocfakehashaaa" for x in specs)
|
||||||
|
|
||||||
|
|
||||||
def test_reusable_externals_cray_manifest(
|
def test_reusable_externals_cray_manifest(temporary_store, manifest_file):
|
||||||
tmpdir, mutable_config, mock_packages, temporary_store, manifest_content
|
|
||||||
):
|
|
||||||
"""The concretizer should be able to reuse specs imported from a manifest without a
|
"""The concretizer should be able to reuse specs imported from a manifest without a
|
||||||
externals config entry in packages.yaml"""
|
externals config entry in packages.yaml"""
|
||||||
with tmpdir.as_cwd():
|
cray_manifest.read(path=str(manifest_file), apply_updates=True)
|
||||||
with open("external-db.json", "w", encoding="utf-8") as f:
|
|
||||||
json.dump(manifest_content, f)
|
|
||||||
cray_manifest.read(path="external-db.json", apply_updates=True)
|
|
||||||
|
|
||||||
# Get any imported spec
|
# Get any imported spec
|
||||||
spec = temporary_store.db.query_local()[0]
|
spec = temporary_store.db.query_local()[0]
|
||||||
|
|
||||||
# Reusable if imported locally
|
# Reusable if imported locally
|
||||||
assert spack.solver.asp._is_reusable(spec, packages={}, local=True)
|
assert spack.solver.asp._is_reusable(spec, packages={}, local=True)
|
||||||
|
|
||||||
# If cray manifest entries end up in a build cache somehow, they are not reusable
|
# If cray manifest entries end up in a build cache somehow, they are not reusable
|
||||||
assert not spack.solver.asp._is_reusable(spec, packages={}, local=False)
|
assert not spack.solver.asp._is_reusable(spec, packages={}, local=False)
|
||||||
|
Loading…
Reference in New Issue
Block a user