unit-test: fix reading Cray manifest files

This commit is contained in:
Massimiliano Culpo 2024-12-18 16:05:58 +01:00
parent 28d42eed5e
commit eb85f2e862
No known key found for this signature in database
GPG Key ID: 3E52BB992233066C

View File

@ -27,6 +27,13 @@
import spack.store
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:
def __init__(self, name, hash, prefix, version, arch, compiler, dependencies, parameters):
@ -69,27 +76,24 @@ def compiler_json(self):
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.version = version
if not arch:
arch = JsonArchEntry("anyplatform", "anyos", "anytarget")
if not executables:
executables = {
"cc": "/path/to/compiler/cc",
"cxx": "/path/to/compiler/cxx",
"fc": "/path/to/compiler/fc",
}
self.arch = arch
self.executables = executables
self.arch = arch or JsonArchEntry("anyplatform", "anyos", "anytarget")
self.executables = executables or {"cc": "cc", "cxx": "cxx", "fc": "fc"}
self.prefix = prefix
def compiler_json(self):
return {
result = {
"name": self.name,
"version": self.version,
"arch": self.arch.compiler_json(),
"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):
"""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
def test_compiler_from_entry():
compiler_data = json.loads(
"""\
{
"name": "gcc",
"prefix": "/path/to/compiler/",
"version": "7.5.0",
"arch": {
"os": "centos8",
"target": "x86_64"
},
"executables": {
"cc": "/path/to/compiler/cc",
"cxx": "/path/to/compiler/cxx",
"fc": "/path/to/compiler/fc"
}
}
"""
def test_compiler_from_entry(mock_executable):
"""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")
fc = mock_executable("gfortran", output="echo 7.5.0")
compiler = compiler_from_entry(
JsonCompilerEntry(
name="gcc",
version="7.5.0",
arch=JsonArchEntry(platform="linux", os="centos8", target="x86_64"),
prefix=str(cc.parent),
executables={"cc": "gcc", "cxx": "g++", "fc": "gfortran"},
).compiler_json(),
manifest_path="/example/file",
)
compiler = compiler_from_entry(compiler_data, "/example/file")
assert compiler.cc == "/path/to/compiler/cc"
assert compiler.cxx == "/path/to/compiler/cxx"
assert compiler.fc == "/path/to/compiler/fc"
assert compiler.operating_system == "centos8"
assert compiler.satisfies("gcc@7.5.0 target=x86_64 os=centos8")
assert compiler.extra_attributes["compilers"]["c"] == str(cc)
assert compiler.extra_attributes["compilers"]["cxx"] == str(cxx)
assert compiler.extra_attributes["compilers"]["fortran"] == str(fc)
@pytest.fixture
@ -262,7 +263,7 @@ def the_host_is_linux():
cray_arch = JsonArchEntry(platform="cray", os="rhel8", target="x86_64")
spec_json = JsonSpecEntry(
name="cray-mpich",
name="mpich",
hash="craympichfakehashaaa",
prefix="/path/to/cray-mpich/",
version="1.0.0",
@ -276,30 +277,12 @@ def the_host_is_linux():
assert spec.architecture.platform == "linux"
def test_translate_compiler_name(_common_arch):
nvidia_compiler = JsonCompilerEntry(
name="nvidia",
version="19.1",
arch=_common_arch,
executables={"cc": "/path/to/compiler/nvc", "cxx": "/path/to/compiler/nvc++"},
@pytest.mark.parametrize(
"name_in_manifest,expected_name",
[("nvidia", "nvhpc"), ("rocm", "llvm-amdgpu"), ("clang", "llvm")],
)
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_translated_compiler_name(name_in_manifest, expected_name):
assert cray_manifest.translated_compiler_name(name_in_manifest) == expected_name
def test_failed_translate_compiler_name(_common_arch):
@ -326,11 +309,9 @@ def test_failed_translate_compiler_name(_common_arch):
@pytest.fixture
def manifest_content(generate_openmpi_entries, _common_compiler, _other_compiler):
return {
# Note: the cray_manifest module doesn't use the _meta section right
# now, but it is anticipated to be useful
"_meta": {
"file-type": "cray-pe-json",
"system-type": "test",
"system-type": "EX",
"schema-version": "1.3",
"cpe-version": "22.06",
},
@ -339,85 +320,59 @@ def manifest_content(generate_openmpi_entries, _common_compiler, _other_compiler
}
def test_read_cray_manifest(
tmpdir, mutable_config, mock_packages, mutable_database, manifest_content
):
def test_read_cray_manifest(temporary_store, manifest_file):
"""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.
"""
with tmpdir.as_cwd():
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)
cray_manifest.read(str(manifest_file), True)
query_specs = temporary_store.db.query("openmpi")
assert any(x.dag_hash() == "openmpifakehasha" for x in query_specs)
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(temporary_store, manifest_file, 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)
concretized_specs = spack.cmd.parse_specs(
"depends-on-openmpi ^/openmpifakehasha".split(), concretize=True
)
assert concretized_specs[0]["hwloc"].dag_hash() == "hwlocfakehashaaa"
def test_read_cray_manifest_add_compiler_failure(
tmpdir, mutable_config, mock_packages, mutable_database, manifest_content, monkeypatch
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
be added.
"""
orig_add_compiler_to_config = spack.compilers.config.add_compiler_to_config
def _mock(entry, *, manifest_path):
return spack.spec.Spec(f"{entry['name']}@{entry['version']}", external_path=str(tmp_path))
class fail_for_clang:
def __init__(self):
self.called_with_clang = False
def __call__(self, compiler, **kwargs):
if compiler.name == "clang":
self.called_with_clang = True
raise Exception()
return orig_add_compiler_to_config(compiler, **kwargs)
checker = fail_for_clang()
monkeypatch.setattr(spack.compilers.config, "add_compiler_to_config", checker)
with tmpdir.as_cwd():
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)
assert checker.called_with_clang
def test_read_cray_manifest_twice_no_compiler_duplicates(
tmpdir, mutable_config, mock_packages, mutable_database, manifest_content
):
with tmpdir.as_cwd():
test_db_fname = "external-db.json"
with open(test_db_fname, "w", encoding="utf-8") as db_file:
json.dump(manifest_content, db_file)
monkeypatch.setattr(cray_manifest, "compiler_from_entry", _mock)
# Read the manifest twice
cray_manifest.read(test_db_fname, True)
cray_manifest.read(test_db_fname, True)
cray_manifest.read(str(manifest_file), True)
cray_manifest.read(str(manifest_file), 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
config_data = mutable_config.get("packages")["gcc"]
assert "externals" in config_data
specs = [spack.spec.Spec(x["spec"]) for x in config_data["externals"]]
assert len(specs) == len(set(specs))
assert len([c for c in specs if c.satisfies("gcc@10.2.0.2112")]) == 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(
def test_read_old_manifest_v1_2(tmp_path, temporary_store):
"""Test reading a file using the older format ('version' instead of 'schema-version')."""
manifest = tmp_path / "manifest_dir" / "test.json"
manifest.parent.mkdir(parents=True)
manifest.write_text(
"""\
{
"_meta": {
@ -429,10 +384,10 @@ def test_read_old_manifest_v1_2(tmpdir, mutable_config, mock_packages, mutable_d
}
"""
)
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"))
# Does not parse as valid JSON
invalid_json_path = os.path.join(manifest_dir, "invalid-json.json")
@ -464,42 +419,34 @@ def test_convert_validation_error(tmpdir, mutable_config, mock_packages, mutable
)
with pytest.raises(cray_manifest.ManifestValidationError) as e:
cray_manifest.read(invalid_schema_path, True)
str(e)
@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'."""
with tmpdir.as_cwd():
test_db_fname = "external-db.json"
with open(test_db_fname, "w", encoding="utf-8") as db_file:
filename = tmp_path / "external-db.json"
with open(filename, "w", encoding="utf-8") as db_file:
json.dump(manifest_content, db_file)
yield str(tmpdir)
return filename
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
contains a manifest file. Ensure that the specs are read.
"""
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)
specs = spack.store.STORE.db.query("hwloc")
specs = temporary_store.db.query("hwloc")
assert any(x.dag_hash() == "hwlocfakehashaaa" for x in specs)
def test_reusable_externals_cray_manifest(
tmpdir, mutable_config, mock_packages, temporary_store, manifest_content
):
def test_reusable_externals_cray_manifest(temporary_store, manifest_file):
"""The concretizer should be able to reuse specs imported from a manifest without a
externals config entry in packages.yaml"""
with tmpdir.as_cwd():
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)
cray_manifest.read(path=str(manifest_file), apply_updates=True)
# Get any imported spec
spec = temporary_store.db.query_local()[0]