# 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 pytest import spack.directives import spack.fetch_strategy import spack.repo from spack.paths import mock_packages_path from spack.spec import Spec from spack.util.naming import mod_to_class from spack.version import VersionChecksumError def pkg_factory(name): """Return a package object tied to an abstract spec""" pkg_cls = spack.repo.PATH.get_pkg_class(name) return pkg_cls(Spec(name)) @pytest.mark.usefixtures("config", "mock_packages") class TestPackage: def test_load_package(self): spack.repo.PATH.get_pkg_class("mpich") def test_package_name(self): pkg_cls = spack.repo.PATH.get_pkg_class("mpich") assert pkg_cls.name == "mpich" def test_package_filename(self): repo = spack.repo.Repo(mock_packages_path) filename = repo.filename_for_package_name("mpich") assert filename == os.path.join(mock_packages_path, "packages", "mpich", "package.py") def test_nonexisting_package_filename(self): repo = spack.repo.Repo(mock_packages_path) filename = repo.filename_for_package_name("some-nonexisting-package") assert filename == os.path.join( mock_packages_path, "packages", "some-nonexisting-package", "package.py" ) def test_package_class_names(self): assert "Mpich" == mod_to_class("mpich") assert "PmgrCollective" == mod_to_class("pmgr_collective") assert "PmgrCollective" == mod_to_class("pmgr-collective") assert "Pmgrcollective" == mod_to_class("PmgrCollective") assert "_3db" == mod_to_class("3db") # Below tests target direct imports of spack packages from the # spack.pkg namespace def test_import_package(self): import spack.pkg.builtin.mock.mpich # type: ignore[import] # noqa: F401 def test_import_package_as(self): import spack.pkg.builtin.mock # noqa: F401 import spack.pkg.builtin.mock as m # noqa: F401 import spack.pkg.builtin.mock.mpich as mp # noqa: F401 from spack.pkg.builtin import mock # noqa: F401 def test_inheritance_of_diretives(self): pkg_cls = spack.repo.PATH.get_pkg_class("simple-inheritance") # Check dictionaries that should have been filled by directives assert len(pkg_cls.dependencies) == 3 assert "cmake" in pkg_cls.dependencies assert "openblas" in pkg_cls.dependencies assert "mpi" in pkg_cls.dependencies assert len(pkg_cls.provided) == 2 # Check that Spec instantiation behaves as we expect s = Spec("simple-inheritance").concretized() assert "^cmake" in s assert "^openblas" in s assert "+openblas" in s assert "mpi" in s s = Spec("simple-inheritance~openblas").concretized() assert "^cmake" in s assert "^openblas" not in s assert "~openblas" in s assert "mpi" in s @pytest.mark.regression("11844") def test_inheritance_of_patches(self): s = Spec("patch-inheritance") # Will error if inheritor package cannot find inherited patch files s.concretize() def test_import_class_from_package(self): from spack.pkg.builtin.mock.mpich import Mpich # noqa: F401 def test_import_module_from_package(self): from spack.pkg.builtin.mock import mpich # noqa: F401 def test_import_namespace_container_modules(self): import spack.pkg # noqa: F401 import spack.pkg as p # noqa: F401 import spack.pkg.builtin # noqa: F401 import spack.pkg.builtin as b # noqa: F401 import spack.pkg.builtin.mock # noqa: F401 import spack.pkg.builtin.mock as m # noqa: F401 from spack import pkg # noqa: F401 from spack.pkg import builtin # noqa: F401 from spack.pkg.builtin import mock # noqa: F401 @pytest.mark.regression("2737") def test_urls_for_versions(mock_packages, config): """Version directive without a 'url' argument should use default url.""" for spec_str in ("url_override@0.9.0", "url_override@1.0.0"): s = Spec(spec_str).concretized() url = s.package.url_for_version("0.9.0") assert url == "http://www.anothersite.org/uo-0.9.0.tgz" url = s.package.url_for_version("1.0.0") assert url == "http://www.doesnotexist.org/url_override-1.0.0.tar.gz" url = s.package.url_for_version("0.8.1") assert url == "http://www.doesnotexist.org/url_override-0.8.1.tar.gz" def test_url_for_version_with_no_urls(mock_packages, config): spec = Spec("git-test") pkg_cls = spack.repo.PATH.get_pkg_class(spec.name) with pytest.raises(spack.package_base.NoURLError): pkg_cls(spec).url_for_version("1.0") with pytest.raises(spack.package_base.NoURLError): pkg_cls(spec).url_for_version("1.1") def test_custom_cmake_prefix_path(mock_packages, config): spec = Spec("depends-on-define-cmake-prefix-paths").concretized() assert spack.build_environment.get_cmake_prefix_path(spec.package) == [ spec["define-cmake-prefix-paths"].prefix.test ] def test_url_for_version_with_only_overrides(mock_packages, config): s = Spec("url-only-override").concretized() # these exist and should just take the URL provided in the package assert s.package.url_for_version("1.0.0") == "http://a.example.com/url_override-1.0.0.tar.gz" assert s.package.url_for_version("0.9.0") == "http://b.example.com/url_override-0.9.0.tar.gz" assert s.package.url_for_version("0.8.1") == "http://c.example.com/url_override-0.8.1.tar.gz" # these don't exist but should still work, even if there are only overrides assert s.package.url_for_version("1.0.5") == "http://a.example.com/url_override-1.0.5.tar.gz" assert s.package.url_for_version("0.9.5") == "http://b.example.com/url_override-0.9.5.tar.gz" assert s.package.url_for_version("0.8.5") == "http://c.example.com/url_override-0.8.5.tar.gz" assert s.package.url_for_version("0.7.0") == "http://c.example.com/url_override-0.7.0.tar.gz" def test_url_for_version_with_only_overrides_with_gaps(mock_packages, config): s = Spec("url-only-override-with-gaps").concretized() # same as for url-only-override -- these are specific assert s.package.url_for_version("1.0.0") == "http://a.example.com/url_override-1.0.0.tar.gz" assert s.package.url_for_version("0.9.0") == "http://b.example.com/url_override-0.9.0.tar.gz" assert s.package.url_for_version("0.8.1") == "http://c.example.com/url_override-0.8.1.tar.gz" # these don't have specific URLs, but should still work by extrapolation assert s.package.url_for_version("1.0.5") == "http://a.example.com/url_override-1.0.5.tar.gz" assert s.package.url_for_version("0.9.5") == "http://b.example.com/url_override-0.9.5.tar.gz" assert s.package.url_for_version("0.8.5") == "http://c.example.com/url_override-0.8.5.tar.gz" assert s.package.url_for_version("0.7.0") == "http://c.example.com/url_override-0.7.0.tar.gz" @pytest.mark.usefixtures("mock_packages", "config") @pytest.mark.parametrize( "spec_str,expected_type,expected_url", [ ( "git-top-level", spack.fetch_strategy.GitFetchStrategy, "https://example.com/some/git/repo", ), ( "svn-top-level", spack.fetch_strategy.SvnFetchStrategy, "https://example.com/some/svn/repo", ), ("hg-top-level", spack.fetch_strategy.HgFetchStrategy, "https://example.com/some/hg/repo"), ], ) def test_fetcher_url(spec_str, expected_type, expected_url): """Ensure that top-level git attribute can be used as a default.""" fetcher = spack.fetch_strategy.for_package_version(pkg_factory(spec_str), "1.0") assert isinstance(fetcher, expected_type) assert fetcher.url == expected_url @pytest.mark.usefixtures("mock_packages", "config") @pytest.mark.parametrize( "spec_str,version_str,exception_type", [ # Non-url-package ("git-top-level", "1.1", spack.fetch_strategy.ExtrapolationError), # Two VCS specified together ("git-url-svn-top-level", "1.0", spack.fetch_strategy.FetcherConflict), ("git-svn-top-level", "1.0", spack.fetch_strategy.FetcherConflict), ], ) def test_fetcher_errors(spec_str, version_str, exception_type): """Verify that we can't extrapolate versions for non-URL packages.""" with pytest.raises(exception_type): spack.fetch_strategy.for_package_version(pkg_factory(spec_str), version_str) @pytest.mark.usefixtures("mock_packages", "config") @pytest.mark.parametrize( "version_str,expected_url,digest", [ ("2.0", "https://example.com/some/tarball-2.0.tar.gz", "20"), ("2.1", "https://example.com/some/tarball-2.1.tar.gz", "21"), ("2.2", "https://www.example.com/foo2.2.tar.gz", "22"), ("2.3", "https://www.example.com/foo2.3.tar.gz", "23"), ], ) def test_git_url_top_level_url_versions(version_str, expected_url, digest): """Test URL fetch strategy inference when url is specified with git.""" # leading 62 zeros of sha256 hash leading_zeros = "0" * 62 fetcher = spack.fetch_strategy.for_package_version( pkg_factory("git-url-top-level"), version_str ) assert isinstance(fetcher, spack.fetch_strategy.URLFetchStrategy) assert fetcher.url == expected_url assert fetcher.digest == leading_zeros + digest @pytest.mark.usefixtures("mock_packages", "config") @pytest.mark.parametrize( "version_str,tag,commit,branch", [ ("3.0", "v3.0", None, None), ("3.1", "v3.1", "abc31", None), ("3.2", None, None, "releases/v3.2"), ("3.3", None, "abc33", "releases/v3.3"), ("3.4", None, "abc34", None), ("submodules", None, None, None), ("develop", None, None, "develop"), ], ) def test_git_url_top_level_git_versions(version_str, tag, commit, branch): """Test git fetch strategy inference when url is specified with git.""" fetcher = spack.fetch_strategy.for_package_version( pkg_factory("git-url-top-level"), version_str ) assert isinstance(fetcher, spack.fetch_strategy.GitFetchStrategy) assert fetcher.url == "https://example.com/some/git/repo" assert fetcher.tag == tag assert fetcher.commit == commit assert fetcher.branch == branch @pytest.mark.usefixtures("mock_packages", "config") @pytest.mark.parametrize("version_str", ["1.0", "1.1", "1.2", "1.3"]) def test_git_url_top_level_conflicts(version_str): """Test git fetch strategy inference when url is specified with git.""" with pytest.raises(spack.fetch_strategy.FetcherConflict): spack.fetch_strategy.for_package_version(pkg_factory("git-url-top-level"), version_str) def test_rpath_args(mutable_database): """Test a package's rpath_args property.""" rec = mutable_database.get_record("mpich") rpath_args = rec.spec.package.rpath_args assert "-rpath" in rpath_args assert "mpich" in rpath_args def test_bundle_version_checksum(mock_directive_bundle, clear_directive_functions): """Test raising exception on a version checksum with a bundle package.""" with pytest.raises(VersionChecksumError, match="Checksums not allowed"): version = spack.directives.version("1.0", checksum="1badpkg") version(mock_directive_bundle) def test_bundle_patch_directive(mock_directive_bundle, clear_directive_functions): """Test raising exception on a patch directive with a bundle package.""" with pytest.raises( spack.directives.UnsupportedPackageDirective, match="Patches are not allowed" ): patch = spack.directives.patch("mock/patch.txt") patch(mock_directive_bundle) @pytest.mark.usefixtures("mock_packages", "config") @pytest.mark.parametrize( "version_str,digest_end,extra_options", [ ("1.0", "10", {"timeout": 42, "cookie": "foobar"}), ("1.1", "11", {"timeout": 65}), ("1.2", "12", {"cookie": "baz"}), ], ) def test_fetch_options(version_str, digest_end, extra_options): """Test fetch options inference.""" leading_zeros = "000000000000000000000000000000" fetcher = spack.fetch_strategy.for_package_version(pkg_factory("fetch-options"), version_str) assert isinstance(fetcher, spack.fetch_strategy.URLFetchStrategy) assert fetcher.digest == leading_zeros + digest_end assert fetcher.extra_options == extra_options def test_package_deprecated_version(mock_packages, mock_fetch, mock_stage): spec = Spec("deprecated-versions") pkg_cls = spack.repo.PATH.get_pkg_class(spec.name) assert spack.package_base.deprecated_version(pkg_cls, "1.1.0") assert not spack.package_base.deprecated_version(pkg_cls, "1.0.0")