From 553d37a6d62b05f15986a702394f67486fa44e0e Mon Sep 17 00:00:00 2001 From: Massimiliano Culpo Date: Mon, 1 Feb 2021 17:27:00 +0100 Subject: [PATCH] Move context manager to swap the current configuration into spack.config The context manager can be used to swap the current configuration temporarily, for any use case that may need it. --- lib/spack/spack/config.py | 67 ++++++++++++++++++++++------- lib/spack/spack/test/cmd/module.py | 7 +-- lib/spack/spack/test/conftest.py | 68 ++++++++++-------------------- 3 files changed, 78 insertions(+), 64 deletions(-) diff --git a/lib/spack/spack/config.py b/lib/spack/spack/config.py index 94bcca43d0..73d5d06e1c 100644 --- a/lib/spack/spack/config.py +++ b/lib/spack/spack/config.py @@ -29,6 +29,7 @@ """ import collections +import contextlib import copy import functools import os @@ -49,6 +50,7 @@ import spack.paths import spack.architecture +import spack.compilers import spack.schema import spack.schema.compilers import spack.schema.mirrors @@ -804,22 +806,6 @@ def _config(): config = llnl.util.lang.Singleton(_config) -def replace_config(configuration): - """Replace the current global configuration with the instance passed as - argument. - - Args: - configuration (Configuration): the new configuration to be used. - - Returns: - The old configuration that has been removed - """ - global config - config.clear_caches(), configuration.clear_caches() - old_config, config = config, configuration - return old_config - - def get(path, default=None, scope=None): """Module-level wrapper for ``Configuration.get()``.""" return config.get(path, default, scope) @@ -1134,6 +1120,55 @@ def ensure_latest_format_fn(section): return update_fn +@contextlib.contextmanager +def use_configuration(*scopes_or_paths): + """Use the configuration scopes passed as arguments within the + context manager. + + Args: + *scopes_or_paths: scope objects or paths to be used + + Returns: + Configuration object associated with the scopes passed as arguments + """ + global config + + # Normalize input and construct a Configuration object + configuration = _config_from(scopes_or_paths) + config.clear_caches(), configuration.clear_caches() + + # Save and clear the current compiler cache + saved_compiler_cache = spack.compilers._cache_config_file + spack.compilers._cache_config_file = [] + + saved_config, config = config, configuration + + yield configuration + + # Restore previous config files + spack.compilers._cache_config_file = saved_compiler_cache + config = saved_config + + +@llnl.util.lang.memoized +def _config_from(scopes_or_paths): + scopes = [] + for scope_or_path in scopes_or_paths: + # If we have a config scope we are already done + if isinstance(scope_or_path, ConfigScope): + scopes.append(scope_or_path) + continue + + # Otherwise we need to construct it + path = os.path.normpath(scope_or_path) + assert os.path.isdir(path), '"{0}" must be a directory'.format(path) + name = os.path.basename(path) + scopes.append(ConfigScope(name, path)) + + configuration = Configuration(*scopes) + return configuration + + class ConfigError(SpackError): """Superclass for all Spack config related errors.""" diff --git a/lib/spack/spack/test/cmd/module.py b/lib/spack/spack/test/cmd/module.py index b328fb6cbf..3917b391fc 100644 --- a/lib/spack/spack/test/cmd/module.py +++ b/lib/spack/spack/test/cmd/module.py @@ -8,10 +8,10 @@ import pytest +import spack.config import spack.main import spack.modules import spack.store -from spack.test.conftest import use_configuration module = spack.main.SpackCommand('module') @@ -19,11 +19,12 @@ #: make sure module files are generated for all the tests here @pytest.fixture(scope='module', autouse=True) def ensure_module_files_are_there( - mock_repo_path, mock_store, mock_configuration): + mock_repo_path, mock_store, mock_configuration_scopes +): """Generate module files for module tests.""" module = spack.main.SpackCommand('module') with spack.store.use_store(mock_store): - with use_configuration(mock_configuration): + with spack.config.use_configuration(*mock_configuration_scopes): with spack.repo.use_repositories(mock_repo_path): module('tcl', 'refresh', '-y') diff --git a/lib/spack/spack/test/conftest.py b/lib/spack/spack/test/conftest.py index 319bb233cc..da792b41f8 100644 --- a/lib/spack/spack/test/conftest.py +++ b/lib/spack/spack/test/conftest.py @@ -4,7 +4,6 @@ # SPDX-License-Identifier: (Apache-2.0 OR MIT) import collections -import contextlib import errno import inspect import itertools @@ -336,7 +335,7 @@ def test_platform(): # -# Context managers used by fixtures +# Note on context managers used by fixtures # # Because these context managers modify global state, they should really # ONLY be used persistently (i.e., around yield statements) in @@ -357,23 +356,6 @@ def test_platform(): # *USE*, or things can get really confusing. # -@contextlib.contextmanager -def use_configuration(config): - """Context manager to swap out the global Spack configuration.""" - saved = spack.config.replace_config(config) - - # Avoid using real spack configuration that has been cached by other - # tests, and avoid polluting the cache with spack test configuration - # (including modified configuration) - saved_compiler_cache = spack.compilers._cache_config_file - spack.compilers._cache_config_file = [] - - yield - - spack.config.replace_config(saved) - spack.compilers._cache_config_file = saved_compiler_cache - - # # Test-specific fixtures # @@ -430,9 +412,7 @@ def default_config(): This ensures we can test the real default configuration without having tests fail when the user overrides the defaults that we test against.""" defaults_path = os.path.join(spack.paths.etc_path, 'spack', 'defaults') - defaults_scope = spack.config.ConfigScope('defaults', defaults_path) - defaults_config = spack.config.Configuration(defaults_scope) - with use_configuration(defaults_config): + with spack.config.use_configuration(defaults_path) as defaults_config: yield defaults_config @@ -507,7 +487,7 @@ def configuration_dir(tmpdir_factory, linux_os): @pytest.fixture(scope='session') -def mock_configuration(configuration_dir): +def mock_configuration_scopes(configuration_dir): """Create a persistent Configuration object from the configuration_dir.""" defaults = spack.config.InternalConfigScope( '_builtin', spack.config.config_defaults @@ -518,14 +498,14 @@ def mock_configuration(configuration_dir): for name in ['site', 'system', 'user']] test_scopes.append(spack.config.InternalConfigScope('command_line')) - yield spack.config.Configuration(*test_scopes) + yield test_scopes @pytest.fixture(scope='function') -def config(mock_configuration): +def config(mock_configuration_scopes): """This fixture activates/deactivates the mock configuration.""" - with use_configuration(mock_configuration): - yield mock_configuration + with spack.config.use_configuration(*mock_configuration_scopes) as config: + yield config @pytest.fixture(scope='function') @@ -534,11 +514,10 @@ def mutable_config(tmpdir_factory, configuration_dir): mutable_dir = tmpdir_factory.mktemp('mutable_config').join('tmp') configuration_dir.copy(mutable_dir) - cfg = spack.config.Configuration( - *[spack.config.ConfigScope(name, str(mutable_dir.join(name))) - for name in ['site', 'system', 'user']]) + scopes = [spack.config.ConfigScope(name, str(mutable_dir.join(name))) + for name in ['site', 'system', 'user']] - with use_configuration(cfg): + with spack.config.use_configuration(*scopes) as cfg: yield cfg @@ -546,23 +525,20 @@ def mutable_config(tmpdir_factory, configuration_dir): def mutable_empty_config(tmpdir_factory, configuration_dir): """Empty configuration that can be modified by the tests.""" mutable_dir = tmpdir_factory.mktemp('mutable_config').join('tmp') + scopes = [spack.config.ConfigScope(name, str(mutable_dir.join(name))) + for name in ['site', 'system', 'user']] - cfg = spack.config.Configuration( - *[spack.config.ConfigScope(name, str(mutable_dir.join(name))) - for name in ['site', 'system', 'user']]) - - with use_configuration(cfg): + with spack.config.use_configuration(*scopes) as cfg: yield cfg @pytest.fixture() def mock_low_high_config(tmpdir): """Mocks two configuration scopes: 'low' and 'high'.""" - config = spack.config.Configuration( - *[spack.config.ConfigScope(name, str(tmpdir.join(name))) - for name in ['low', 'high']]) + scopes = [spack.config.ConfigScope(name, str(tmpdir.join(name))) + for name in ['low', 'high']] - with use_configuration(config): + with spack.config.use_configuration(*scopes) as config: yield config @@ -611,7 +587,7 @@ def _store_dir_and_cache(tmpdir_factory): @pytest.fixture(scope='session') -def mock_store(tmpdir_factory, mock_repo_path, mock_configuration, +def mock_store(tmpdir_factory, mock_repo_path, mock_configuration_scopes, _store_dir_and_cache): """Creates a read-only mock database with some packages installed note that the ref count for dyninst here will be 3, as it's recycled @@ -625,7 +601,7 @@ def mock_store(tmpdir_factory, mock_repo_path, mock_configuration, # If the cache does not exist populate the store and create it if not os.path.exists(str(store_cache.join('.spack-db'))): - with use_configuration(mock_configuration): + with spack.config.use_configuration(*mock_configuration_scopes): with spack.store.use_store(str(store_path)) as store: with spack.repo.use_repositories(mock_repo_path): _populate(store.db) @@ -640,8 +616,10 @@ def mock_store(tmpdir_factory, mock_repo_path, mock_configuration, @pytest.fixture(scope='function') -def mutable_mock_store(tmpdir_factory, mock_repo_path, mock_configuration, - _store_dir_and_cache): +def mutable_mock_store( + tmpdir_factory, mock_repo_path, mock_configuration_scopes, + _store_dir_and_cache +): """Creates a read-only mock database with some packages installed note that the ref count for dyninst here will be 3, as it's recycled across each install. @@ -654,7 +632,7 @@ def mutable_mock_store(tmpdir_factory, mock_repo_path, mock_configuration, # If the cache does not exist populate the store and create it if not os.path.exists(str(store_cache.join('.spack-db'))): - with use_configuration(mock_configuration): + with spack.config.use_configuration(*mock_configuration_scopes): with spack.store.use_store(str(store_path)) as store: with spack.repo.use_repositories(mock_repo_path): _populate(store.db)