tests/cmd/external.py: use mock packages (#50363)

Signed-off-by: Massimiliano Culpo <massimiliano.culpo@gmail.com>
Co-authored-by: Massimiliano Culpo <massimiliano.culpo@gmail.com>
This commit is contained in:
Tamara Dahlgren 2025-05-15 07:59:35 -07:00 committed by GitHub
parent f1ba23316b
commit 0d586695a0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 91 additions and 21 deletions

View File

@ -18,6 +18,8 @@
from spack.main import SpackCommand from spack.main import SpackCommand
from spack.spec import Spec from spack.spec import Spec
pytestmark = [pytest.mark.usefixtures("mock_packages")]
@pytest.fixture @pytest.fixture
def executables_found(monkeypatch): def executables_found(monkeypatch):
@ -67,13 +69,24 @@ def test_get_executables(working_env, mock_executable):
# TODO: this test should be made to work, but in the meantime it is # TODO: this test should be made to work, but in the meantime it is
# causing intermittent (spurious) CI failures on all PRs # causing intermittent (spurious) CI failures on all PRs
@pytest.mark.not_on_windows("Test fails intermittently on Windows") @pytest.mark.not_on_windows("Test fails intermittently on Windows")
def test_find_external_cmd_not_buildable(mutable_config, working_env, mock_executable): def test_find_external_cmd_not_buildable(
mutable_config, working_env, mock_executable, monkeypatch
):
"""When the user invokes 'spack external find --not-buildable', the config """When the user invokes 'spack external find --not-buildable', the config
for any package where Spack finds an external version should be marked as for any package where Spack finds an external version should be marked as
not buildable. not buildable.
""" """
cmake_path1 = mock_executable("cmake", output="echo cmake version 1.foo") version = "1.foo"
os.environ["PATH"] = os.pathsep.join([os.path.dirname(cmake_path1)])
@classmethod
def _determine_version(cls, exe):
return version
cmake_cls = spack.repo.PATH.get_pkg_class("cmake")
monkeypatch.setattr(cmake_cls, "determine_version", _determine_version)
cmake_path = mock_executable("cmake", output=f"echo cmake version {version}")
os.environ["PATH"] = str(cmake_path.parent)
external("find", "--not-buildable", "cmake") external("find", "--not-buildable", "cmake")
pkgs_cfg = spack.config.get("packages") pkgs_cfg = spack.config.get("packages")
assert "cmake" in pkgs_cfg assert "cmake" in pkgs_cfg
@ -89,10 +102,12 @@ def test_find_external_cmd_not_buildable(mutable_config, working_env, mock_execu
["detectable"], ["detectable"],
[], [],
[ [
"builtin.mock.cmake",
"builtin.mock.find-externals1", "builtin.mock.find-externals1",
"builtin.mock.gcc", "builtin.mock.gcc",
"builtin.mock.llvm",
"builtin.mock.intel-oneapi-compilers", "builtin.mock.intel-oneapi-compilers",
"builtin.mock.llvm",
"builtin.mock.mpich",
], ],
), ),
# find --all --exclude find-externals1 # find --all --exclude find-externals1
@ -100,26 +115,38 @@ def test_find_external_cmd_not_buildable(mutable_config, working_env, mock_execu
None, None,
["detectable"], ["detectable"],
["builtin.mock.find-externals1"], ["builtin.mock.find-externals1"],
["builtin.mock.gcc", "builtin.mock.llvm", "builtin.mock.intel-oneapi-compilers"], [
"builtin.mock.cmake",
"builtin.mock.gcc",
"builtin.mock.intel-oneapi-compilers",
"builtin.mock.llvm",
"builtin.mock.mpich",
],
), ),
( (
None, None,
["detectable"], ["detectable"],
["find-externals1"], ["find-externals1"],
["builtin.mock.gcc", "builtin.mock.llvm", "builtin.mock.intel-oneapi-compilers"], [
"builtin.mock.cmake",
"builtin.mock.gcc",
"builtin.mock.intel-oneapi-compilers",
"builtin.mock.llvm",
"builtin.mock.mpich",
],
), ),
# find cmake (and cmake is not detectable) # find hwloc (and mock hwloc is not detectable)
(["cmake"], ["detectable"], [], []), (["hwloc"], ["detectable"], [], []),
], ],
) )
def test_package_selection(names, tags, exclude, expected, mutable_mock_repo): def test_package_selection(names, tags, exclude, expected):
"""Tests various cases of selecting packages""" """Tests various cases of selecting packages"""
# In the mock repo we only have 'find-externals1' that is detectable # In the mock repo we only have 'find-externals1' that is detectable
result = spack.cmd.external.packages_to_search_for(names=names, tags=tags, exclude=exclude) result = spack.cmd.external.packages_to_search_for(names=names, tags=tags, exclude=exclude)
assert set(result) == set(expected) assert set(result) == set(expected)
def test_find_external_no_manifest(mutable_config, working_env, mutable_mock_repo, monkeypatch): def test_find_external_no_manifest(mutable_config, working_env, monkeypatch):
"""The user runs 'spack external find'; the default path for storing """The user runs 'spack external find'; the default path for storing
manifest files does not exist. Ensure that the command does not manifest files does not exist. Ensure that the command does not
fail. fail.
@ -132,7 +159,7 @@ def test_find_external_no_manifest(mutable_config, working_env, mutable_mock_rep
def test_find_external_empty_default_manifest_dir( def test_find_external_empty_default_manifest_dir(
mutable_config, working_env, mutable_mock_repo, tmpdir, monkeypatch mutable_config, working_env, tmpdir, monkeypatch
): ):
"""The user runs 'spack external find'; the default path for storing """The user runs 'spack external find'; the default path for storing
manifest files exists but is empty. Ensure that the command does not manifest files exists but is empty. Ensure that the command does not
@ -147,7 +174,7 @@ def test_find_external_empty_default_manifest_dir(
@pytest.mark.not_on_windows("Can't chmod on Windows") @pytest.mark.not_on_windows("Can't chmod on Windows")
@pytest.mark.skipif(getuid() == 0, reason="user is root") @pytest.mark.skipif(getuid() == 0, reason="user is root")
def test_find_external_manifest_with_bad_permissions( def test_find_external_manifest_with_bad_permissions(
mutable_config, working_env, mutable_mock_repo, tmpdir, monkeypatch mutable_config, working_env, tmpdir, monkeypatch
): ):
"""The user runs 'spack external find'; the default path for storing """The user runs 'spack external find'; the default path for storing
manifest files exists but with insufficient permissions. Check that manifest files exists but with insufficient permissions. Check that
@ -167,7 +194,7 @@ def test_find_external_manifest_with_bad_permissions(
os.chmod(test_manifest_file_path, 0o700) os.chmod(test_manifest_file_path, 0o700)
def test_find_external_manifest_failure(mutable_config, mutable_mock_repo, tmpdir, monkeypatch): def test_find_external_manifest_failure(mutable_config, tmpdir, monkeypatch):
"""The user runs 'spack external find'; the manifest parsing fails with """The user runs 'spack external find'; the manifest parsing fails with
some exception. Ensure that the command still succeeds (i.e. moves on some exception. Ensure that the command still succeeds (i.e. moves on
to other external detection mechanisms). to other external detection mechanisms).
@ -187,7 +214,7 @@ def fail():
assert "Skipping manifest and continuing" in output assert "Skipping manifest and continuing" in output
def test_find_external_merge(mutable_config, mutable_mock_repo, tmp_path): def test_find_external_merge(mutable_config, tmp_path):
"""Checks that 'spack find external' doesn't overwrite an existing spec in packages.yaml.""" """Checks that 'spack find external' doesn't overwrite an existing spec in packages.yaml."""
pkgs_cfg_init = { pkgs_cfg_init = {
"find-externals1": { "find-externals1": {
@ -213,7 +240,7 @@ def test_find_external_merge(mutable_config, mutable_mock_repo, tmp_path):
assert {"spec": "find-externals1@1.2", "prefix": "/x/y2"} in pkg_externals assert {"spec": "find-externals1@1.2", "prefix": "/x/y2"} in pkg_externals
def test_list_detectable_packages(mutable_config, mutable_mock_repo): def test_list_detectable_packages(mutable_config):
external("list") external("list")
assert external.returncode == 0 assert external.returncode == 0
@ -259,13 +286,23 @@ def test_new_entries_are_reported_correctly(mock_executable, mutable_config, mon
@pytest.mark.parametrize("command_args", [("-t", "build-tools"), ("-t", "build-tools", "cmake")]) @pytest.mark.parametrize("command_args", [("-t", "build-tools"), ("-t", "build-tools", "cmake")])
@pytest.mark.not_on_windows("the test uses bash scripts")
def test_use_tags_for_detection(command_args, mock_executable, mutable_config, monkeypatch): def test_use_tags_for_detection(command_args, mock_executable, mutable_config, monkeypatch):
versions = {"cmake": "3.19.1", "openssl": "2.8.3"}
@classmethod
def _determine_version(cls, exe):
return versions[os.path.basename(exe)]
cmake_cls = spack.repo.PATH.get_pkg_class("cmake")
monkeypatch.setattr(cmake_cls, "determine_version", _determine_version)
# Prepare an environment to detect a fake cmake # Prepare an environment to detect a fake cmake
cmake_exe = mock_executable("cmake", output="echo cmake version 3.19.1") cmake_exe = mock_executable("cmake", output=f"echo cmake version {versions['cmake']}")
prefix = os.path.dirname(cmake_exe) prefix = os.path.dirname(cmake_exe)
monkeypatch.setenv("PATH", prefix) monkeypatch.setenv("PATH", prefix)
openssl_exe = mock_executable("openssl", output="OpenSSL 2.8.3") openssl_exe = mock_executable("openssl", output=f"OpenSSL {versions['openssl']}")
prefix = os.path.dirname(openssl_exe) prefix = os.path.dirname(openssl_exe)
monkeypatch.setenv("PATH", prefix) monkeypatch.setenv("PATH", prefix)
@ -282,6 +319,16 @@ def test_failures_in_scanning_do_not_result_in_an_error(
mock_executable, monkeypatch, mutable_config mock_executable, monkeypatch, mutable_config
): ):
"""Tests that scanning paths with wrong permissions, won't cause `external find` to error.""" """Tests that scanning paths with wrong permissions, won't cause `external find` to error."""
versions = {"first": "3.19.1", "second": "3.23.3"}
@classmethod
def _determine_version(cls, exe):
bin_parent = os.path.dirname(exe).split(os.sep)[-2]
return versions[bin_parent]
cmake_cls = spack.repo.PATH.get_pkg_class("cmake")
monkeypatch.setattr(cmake_cls, "determine_version", _determine_version)
cmake_exe1 = mock_executable( cmake_exe1 = mock_executable(
"cmake", output="echo cmake version 3.19.1", subdir=("first", "bin") "cmake", output="echo cmake version 3.19.1", subdir=("first", "bin")
) )
@ -299,21 +346,30 @@ def test_failures_in_scanning_do_not_result_in_an_error(
assert external.returncode == 0 assert external.returncode == 0
assert "The following specs have been" in output assert "The following specs have been" in output
assert "cmake" in output assert "cmake" in output
assert "3.23.3" in output for vers in versions.values():
assert "3.19.1" not in output assert vers in output
def test_detect_virtuals(mock_executable, mutable_config, monkeypatch): def test_detect_virtuals(mock_executable, mutable_config, monkeypatch):
"""Test whether external find --not-buildable sets virtuals as non-buildable (unless user """Test whether external find --not-buildable sets virtuals as non-buildable (unless user
config sets them to buildable)""" config sets them to buildable)"""
mpich = mock_executable("mpichversion", output="echo MPICH Version: 4.0.2") version = "4.0.2"
@classmethod
def _determine_version(cls, exe):
return version
cmake_cls = spack.repo.PATH.get_pkg_class("mpich")
monkeypatch.setattr(cmake_cls, "determine_version", _determine_version)
mpich = mock_executable("mpichversion", output=f"echo MPICH Version: {version}")
prefix = os.path.dirname(mpich) prefix = os.path.dirname(mpich)
external("find", "--path", prefix, "--not-buildable", "mpich") external("find", "--path", prefix, "--not-buildable", "mpich")
# Check that mpich was correctly detected # Check that mpich was correctly detected
mpich = mutable_config.get("packages:mpich") mpich = mutable_config.get("packages:mpich")
assert mpich["buildable"] is False assert mpich["buildable"] is False
assert Spec(mpich["externals"][0]["spec"]).satisfies("mpich@4.0.2") assert Spec(mpich["externals"][0]["spec"]).satisfies(f"mpich@{version}")
# Check that the virtual package mpi was marked as non-buildable # Check that the virtual package mpi was marked as non-buildable
assert mutable_config.get("packages:mpi:buildable") is False assert mutable_config.get("packages:mpi:buildable") is False

View File

@ -21,6 +21,7 @@ class Cmake(Package):
url = "https://cmake.org/files/v3.4/cmake-3.4.3.tar.gz" url = "https://cmake.org/files/v3.4/cmake-3.4.3.tar.gz"
tags = ["build-tools"] tags = ["build-tools"]
executables = ["^cmake[0-9]*$"]
depends_on("c", type="build") depends_on("c", type="build")
depends_on("cxx", type="build") depends_on("cxx", type="build")
@ -36,6 +37,12 @@ class Cmake(Package):
url="https://cmake.org/files/v3.4/cmake-3.4.3.tar.gz", url="https://cmake.org/files/v3.4/cmake-3.4.3.tar.gz",
) )
@classmethod
def determine_version(cls, exe):
output = Executable(exe)("--version", output=str, error=str)
match = re.search(r"cmake.*version\s+(\S+)", output)
return match.group(1) if match else None
def setup_build_environment(self, env: EnvironmentModifications) -> None: def setup_build_environment(self, env: EnvironmentModifications) -> None:
spack_cc # Ensure spack module-scope variable is avaiable spack_cc # Ensure spack module-scope variable is avaiable
env.set("for_install", "for_install") env.set("for_install", "for_install")

View File

@ -12,6 +12,7 @@ class Mpich(Package):
list_depth = 2 list_depth = 2
tags = ["tag1", "tag2"] tags = ["tag1", "tag2"]
executables = ["^mpichversion$"]
variant("debug", default=False, description="Compile MPICH with debug flags.") variant("debug", default=False, description="Compile MPICH with debug flags.")
@ -30,6 +31,12 @@ class Mpich(Package):
depends_on("cxx", type="build") depends_on("cxx", type="build")
depends_on("fortran", type="build") depends_on("fortran", type="build")
@classmethod
def determine_version(cls, exe):
output = Executable(exe)(output=str, error=str)
match = re.search(r"MPICH Version:\s+(\S+)", output)
return match.group(1) if match else None
def install(self, spec, prefix): def install(self, spec, prefix):
touch(prefix.mpich) touch(prefix.mpich)