summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMassimiliano Culpo <massimiliano.culpo@gmail.com>2021-02-02 09:57:09 +0100
committerTodd Gamblin <tgamblin@llnl.gov>2021-02-10 16:50:09 -0800
commit4558dc06e21e01ab07a43737b8cb99d1d69abb5d (patch)
treef20dcf1f3f2d1904f25b5ec6ba0b100c7c0cb954
parent553d37a6d62b05f15986a702394f67486fa44e0e (diff)
downloadspack-4558dc06e21e01ab07a43737b8cb99d1d69abb5d.tar.gz
spack-4558dc06e21e01ab07a43737b8cb99d1d69abb5d.tar.bz2
spack-4558dc06e21e01ab07a43737b8cb99d1d69abb5d.tar.xz
spack-4558dc06e21e01ab07a43737b8cb99d1d69abb5d.zip
Added a context manager to swap architectures
This solves a few FIXMEs in conftest.py, where we were manipulating globals and seeing side effects prior to registering fixtures. This commit solves the FIXMEs, but introduces a performance regression on tests that may need to be investigated
-rw-r--r--lib/spack/spack/architecture.py58
-rw-r--r--lib/spack/spack/test/architecture.py35
-rw-r--r--lib/spack/spack/test/cmd/extensions.py2
-rw-r--r--lib/spack/spack/test/cmd/undevelop.py4
-rw-r--r--lib/spack/spack/test/conftest.py23
5 files changed, 88 insertions, 34 deletions
diff --git a/lib/spack/spack/architecture.py b/lib/spack/spack/architecture.py
index 67b2883116..6341df857d 100644
--- a/lib/spack/spack/architecture.py
+++ b/lib/spack/spack/architecture.py
@@ -56,6 +56,7 @@ set. The user can set the front-end and back-end operating setting by the class
attributes front_os and back_os. The operating system as described earlier,
will be responsible for compiler detection.
"""
+import contextlib
import functools
import inspect
import warnings
@@ -67,6 +68,8 @@ import llnl.util.tty as tty
from llnl.util.lang import memoized, list_modules, key_ordering
import spack.compiler
+import spack.compilers
+import spack.config
import spack.paths
import spack.error as serr
import spack.util.executable
@@ -492,7 +495,7 @@ def arch_for_spec(arch_spec):
@memoized
-def all_platforms():
+def _all_platforms():
classes = []
mod_path = spack.paths.platform_path
parent_module = "spack.platforms"
@@ -513,7 +516,7 @@ def all_platforms():
@memoized
-def platform():
+def _platform():
"""Detects the platform for this machine.
Gather a list of all available subclasses of platforms.
@@ -522,7 +525,7 @@ def platform():
a file path (/opt/cray...)
"""
# Try to create a Platform object using the config file FIRST
- platform_list = all_platforms()
+ platform_list = _all_platforms()
platform_list.sort(key=lambda a: a.priority)
for platform_cls in platform_list:
@@ -530,6 +533,19 @@ def platform():
return platform_cls()
+#: The "real" platform of the host running Spack. This should not be changed
+#: by any method and is here as a convenient way to refer to the host platform.
+real_platform = _platform
+
+#: The current platform used by Spack. May be swapped by the use_platform
+#: context manager.
+platform = _platform
+
+#: The list of all platform classes. May be swapped by the use_platform
+#: context manager.
+all_platforms = _all_platforms
+
+
@memoized
def default_arch():
"""Default ``Arch`` object for this machine.
@@ -564,3 +580,39 @@ def compatible_sys_types():
arch = Arch(platform(), 'default_os', target)
compatible_archs.append(str(arch))
return compatible_archs
+
+
+class _PickleableCallable(object):
+ """Class used to pickle a callable that may substitute either
+ _platform or _all_platforms. Lambda or nested functions are
+ not pickleable.
+ """
+ def __init__(self, return_value):
+ self.return_value = return_value
+
+ def __call__(self):
+ return self.return_value
+
+
+@contextlib.contextmanager
+def use_platform(new_platform):
+ global platform, all_platforms
+
+ msg = '"{0}" must be an instance of Platform'
+ assert isinstance(new_platform, Platform), msg.format(new_platform)
+
+ original_platform_fn, original_all_platforms_fn = platform, all_platforms
+ platform = _PickleableCallable(new_platform)
+ all_platforms = _PickleableCallable([type(new_platform)])
+
+ # Clear configuration and compiler caches
+ spack.config.config.clear_caches()
+ spack.compilers._cache_config_files = []
+
+ yield new_platform
+
+ platform, all_platforms = original_platform_fn, original_all_platforms_fn
+
+ # Clear configuration and compiler caches
+ spack.config.config.clear_caches()
+ spack.compilers._cache_config_files = []
diff --git a/lib/spack/spack/test/architecture.py b/lib/spack/spack/test/architecture.py
index 305057b38a..e37818f10c 100644
--- a/lib/spack/spack/test/architecture.py
+++ b/lib/spack/spack/test/architecture.py
@@ -6,6 +6,7 @@
""" Test checks if the architecture class is created correctly and also that
the functions are looking for the correct architecture name
"""
+import itertools
import os
import platform as py_platform
@@ -116,20 +117,26 @@ def test_user_defaults(config):
assert default_target == default_spec.architecture.target
-@pytest.mark.parametrize('operating_system', [
- x for x in spack.architecture.platform().operating_sys
-] + ["fe", "be", "frontend", "backend"])
-@pytest.mark.parametrize('target', [
- x for x in spack.architecture.platform().targets
-] + ["fe", "be", "frontend", "backend"])
-def test_user_input_combination(config, operating_system, target):
- platform = spack.architecture.platform()
- spec = Spec("libelf os=%s target=%s" % (operating_system, target))
- spec.concretize()
- assert spec.architecture.os == str(
- platform.operating_system(operating_system)
- )
- assert spec.architecture.target == platform.target(target)
+def test_user_input_combination(config):
+ valid_keywords = ["fe", "be", "frontend", "backend"]
+
+ possible_targets = ([x for x in spack.architecture.platform().targets]
+ + valid_keywords)
+
+ possible_os = ([x for x in spack.architecture.platform().operating_sys]
+ + valid_keywords)
+
+ for target, operating_system in itertools.product(
+ possible_targets, possible_os
+ ):
+ platform = spack.architecture.platform()
+ spec_str = "libelf os={0} target={1}".format(operating_system, target)
+ spec = Spec(spec_str)
+ spec.concretize()
+ assert spec.architecture.os == str(
+ platform.operating_system(operating_system)
+ )
+ assert spec.architecture.target == platform.target(target)
def test_operating_system_conversion_to_dict():
diff --git a/lib/spack/spack/test/cmd/extensions.py b/lib/spack/spack/test/cmd/extensions.py
index ce2bd1245b..d2093fa887 100644
--- a/lib/spack/spack/test/cmd/extensions.py
+++ b/lib/spack/spack/test/cmd/extensions.py
@@ -27,7 +27,7 @@ def python_database(mock_packages, mutable_database):
@pytest.mark.db
-def test_extensions(mock_packages, python_database, capsys):
+def test_extensions(mock_packages, python_database, config, capsys):
ext2 = Spec("py-extension2").concretized()
def check_output(ni, na):
diff --git a/lib/spack/spack/test/cmd/undevelop.py b/lib/spack/spack/test/cmd/undevelop.py
index 8a8f994b29..0c8f2df978 100644
--- a/lib/spack/spack/test/cmd/undevelop.py
+++ b/lib/spack/spack/test/cmd/undevelop.py
@@ -12,7 +12,7 @@ env = SpackCommand('env')
concretize = SpackCommand('concretize')
-def test_undevelop(tmpdir, mock_packages, mutable_mock_env_path):
+def test_undevelop(tmpdir, config, mock_packages, mutable_mock_env_path):
# setup environment
envdir = tmpdir.mkdir('env')
with envdir.as_cwd():
@@ -39,7 +39,7 @@ env:
assert not after.satisfies('dev_path=*')
-def test_undevelop_nonexistent(tmpdir, mock_packages, mutable_mock_env_path):
+def test_undevelop_nonexistent(tmpdir, config, mock_packages, mutable_mock_env_path):
# setup environment
envdir = tmpdir.mkdir('env')
with envdir.as_cwd():
diff --git a/lib/spack/spack/test/conftest.py b/lib/spack/spack/test/conftest.py
index da792b41f8..74b8a22a12 100644
--- a/lib/spack/spack/test/conftest.py
+++ b/lib/spack/spack/test/conftest.py
@@ -315,24 +315,18 @@ def _skip_if_missing_executables(request):
pytest.skip(msg.format(', '.join(missing_execs)))
-# FIXME: The lines below should better be added to a fixture with
-# FIXME: session-scope. Anyhow doing it is not easy, as it seems
-# FIXME: there's some weird interaction with compilers during concretization.
-spack.architecture.real_platform = spack.architecture.platform # type: ignore
-
-
+@pytest.fixture(scope='session')
def test_platform():
return spack.platforms.test.Test()
-spack.architecture.platform = test_platform
-
-
-# FIXME: Since we change the architecture above, we have to (re)initialize
-# FIXME: the config singleton. If it gets initialized too early with the
-# FIXME: actual architecture, tests will fail.
-spack.config.config = spack.config._config()
-
+@pytest.fixture(autouse=True, scope='session')
+def _use_test_platform(test_platform):
+ # This is the only context manager used at session scope (see note
+ # below for more insight) since we want to use the test platform as
+ # a default during tests.
+ with spack.architecture.use_platform(test_platform):
+ yield
#
# Note on context managers used by fixtures
@@ -356,6 +350,7 @@ spack.config.config = spack.config._config()
# *USE*, or things can get really confusing.
#
+
#
# Test-specific fixtures
#