# 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 os import stat import pytest import spack.cmd.modules import spack.config import spack.error import spack.modules.tcl import spack.package_base import spack.schema.modules import spack.spec from spack.modules.common import UpstreamModuleIndex from spack.spec import Spec pytestmark = [ pytest.mark.not_on_windows("does not run on windows"), pytest.mark.usefixtures("mock_modules_root"), ] def test_update_dictionary_extending_list(): target = {"foo": {"a": 1, "b": 2, "d": 4}, "bar": [1, 2, 4], "baz": "foobar"} update = {"foo": {"c": 3}, "bar": [3], "baz": "foobaz", "newkey": {"d": 4}} spack.modules.common.update_dictionary_extending_lists(target, update) assert len(target) == 4 assert len(target["foo"]) == 4 assert len(target["bar"]) == 4 assert target["baz"] == "foobaz" @pytest.fixture() def mock_module_defaults(monkeypatch): def impl(*args): # No need to patch both types because neither override base monkeypatch.setattr( spack.modules.common.BaseConfiguration, "defaults", [arg for arg in args] ) return impl @pytest.fixture() def mock_package_perms(monkeypatch): perms = stat.S_IRGRP | stat.S_IWGRP monkeypatch.setattr(spack.package_prefs, "get_package_permissions", lambda spec: perms) yield perms def test_modules_written_with_proper_permissions( mock_module_filename, mock_package_perms, mock_packages, config ): spec = spack.spec.Spec("mpileaks").concretized() # The code tested is common to all module types, but has to be tested from # one. Tcl picked at random generator = spack.modules.tcl.TclModulefileWriter(spec, "default") generator.write() assert mock_package_perms & os.stat(mock_module_filename).st_mode == mock_package_perms @pytest.mark.parametrize("module_type", ["tcl", "lmod"]) def test_modules_default_symlink( module_type, mock_packages, mock_module_filename, mock_module_defaults, config ): spec = spack.spec.Spec("mpileaks@2.3").concretized() mock_module_defaults(spec.format("{name}{@version}"), True) generator_cls = spack.modules.module_types[module_type] generator = generator_cls(spec, "default") generator.write() link_path = os.path.join(os.path.dirname(mock_module_filename), "default") assert os.path.islink(link_path) assert os.readlink(link_path) == mock_module_filename generator.remove() assert not os.path.lexists(link_path) class MockDb: def __init__(self, db_ids, spec_hash_to_db): self.upstream_dbs = db_ids self.spec_hash_to_db = spec_hash_to_db def db_for_spec_hash(self, spec_hash): return self.spec_hash_to_db.get(spec_hash) class MockSpec: def __init__(self, unique_id): self.unique_id = unique_id def dag_hash(self): return self.unique_id def test_upstream_module_index(): s1 = MockSpec("spec-1") s2 = MockSpec("spec-2") s3 = MockSpec("spec-3") s4 = MockSpec("spec-4") tcl_module_index = """\ module_index: {0}: path: /path/to/a use_name: a """.format( s1.dag_hash() ) module_indices = [{"tcl": spack.modules.common._read_module_index(tcl_module_index)}, {}] dbs = ["d0", "d1"] mock_db = MockDb(dbs, {s1.dag_hash(): "d0", s2.dag_hash(): "d1", s3.dag_hash(): "d0"}) upstream_index = UpstreamModuleIndex(mock_db, module_indices) m1 = upstream_index.upstream_module(s1, "tcl") assert m1.path == "/path/to/a" # No modules are defined for the DB associated with s2 assert not upstream_index.upstream_module(s2, "tcl") # Modules are defined for the index associated with s1, but none are # defined for the requested type assert not upstream_index.upstream_module(s1, "lmod") # A module is registered with a DB and the associated module index has # modules of the specified type defined, but not for the requested spec assert not upstream_index.upstream_module(s3, "tcl") # The spec isn't recorded as installed in any of the DBs with pytest.raises(spack.error.SpackError): upstream_index.upstream_module(s4, "tcl") def test_get_module_upstream(): s1 = MockSpec("spec-1") tcl_module_index = """\ module_index: {0}: path: /path/to/a use_name: a """.format( s1.dag_hash() ) module_indices = [{}, {"tcl": spack.modules.common._read_module_index(tcl_module_index)}] dbs = ["d0", "d1"] mock_db = MockDb(dbs, {s1.dag_hash(): "d1"}) upstream_index = UpstreamModuleIndex(mock_db, module_indices) setattr(s1, "installed_upstream", True) try: old_index = spack.modules.common.upstream_module_index spack.modules.common.upstream_module_index = upstream_index m1_path = spack.modules.common.get_module("tcl", s1, True) assert m1_path == "/path/to/a" finally: spack.modules.common.upstream_module_index = old_index @pytest.mark.regression("14347") def test_load_installed_package_not_in_repo(install_mockery, mock_fetch, monkeypatch): """Test that installed packages that have been removed are still loadable""" spec = Spec("trivial-install-test-package").concretized() spec.package.do_install() spack.modules.module_types["tcl"](spec, "default", True).write() def find_nothing(*args): raise spack.repo.UnknownPackageError("Repo package access is disabled for test") # Mock deletion of the package spec._package = None monkeypatch.setattr(spack.repo.PATH, "get", find_nothing) with pytest.raises(spack.repo.UnknownPackageError): spec.package module_path = spack.modules.common.get_module("tcl", spec, True) assert module_path spack.package_base.PackageBase.uninstall_by_spec(spec) @pytest.mark.regression("37649") def test_check_module_set_name(mutable_config): """Tests that modules set name are validated correctly and an error is reported if the name we require does not exist or is reserved by the configuration.""" # Minimal modules.yaml config. spack.config.set( "modules", { "prefix_inspections": {"./bin": ["PATH"]}, # module sets "first": {}, "second": {}, }, ) # Valid module set name spack.cmd.modules.check_module_set_name("first") # Invalid module set names msg = "Valid module set names are" with pytest.raises(spack.config.ConfigError, match=msg): spack.cmd.modules.check_module_set_name("prefix_inspections") with pytest.raises(spack.config.ConfigError, match=msg): spack.cmd.modules.check_module_set_name("third")