From 87cdfa2c255e86b9f89790349f2d8479c0eb86b8 Mon Sep 17 00:00:00 2001 From: Tamara Dahlgren Date: Tue, 17 Sep 2019 19:27:33 -0700 Subject: Add support for nested "overrides" scopes. --- lib/spack/spack/config.py | 26 +++++++++++++++++++++-- lib/spack/spack/test/config.py | 48 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/lib/spack/spack/config.py b/lib/spack/spack/config.py index c73b5047f5..4a684986eb 100644 --- a/lib/spack/spack/config.py +++ b/lib/spack/spack/config.py @@ -32,6 +32,7 @@ schemas are in submodules of :py:mod:`spack.schema`. import copy import os +import re import sys import multiprocessing from contextlib import contextmanager @@ -354,6 +355,15 @@ class Configuration(object): """Non-internal scope with highest precedence.""" return next(reversed(self.file_scopes), None) + def matching_scopes(self, reg_expr): + """ + List of all scopes whose names match the provided regular expression. + + For example, matching_scopes(r'^command') will return all scopes + whose names begin with `command`. + """ + return [s for s in self.scopes.values() if re.search(reg_expr, s.name)] + def _validate_scope(self, scope): """Ensure that scope is valid in this configuration. @@ -539,13 +549,25 @@ def override(path_or_scope, value=None): an internal config scope for it and push/pop that scope. """ + base_name = 'overrides-' if isinstance(path_or_scope, ConfigScope): overrides = path_or_scope config.push_scope(path_or_scope) else: - overrides = InternalConfigScope('overrides') + # Ensure the new override gets a unique scope name + current_overrides = [s.name for s in + config.matching_scopes(r'^{0}'.format(base_name))] + num_overrides = len(current_overrides) + while True: + scope_name = '{0}{1}'.format(base_name, num_overrides) + if scope_name in current_overrides: + num_overrides += 1 + else: + break + + overrides = InternalConfigScope(scope_name) config.push_scope(overrides) - config.set(path_or_scope, value, scope='overrides') + config.set(path_or_scope, value, scope=scope_name) yield config diff --git a/lib/spack/spack/test/config.py b/lib/spack/spack/test/config.py index 163fc392d3..99e1d7499c 100644 --- a/lib/spack/spack/test/config.py +++ b/lib/spack/spack/test/config.py @@ -627,6 +627,54 @@ config: spack.config._add_command_line_scopes(mutable_config, [str(tmpdir)]) +@pytest.mark.nomockstage +def test_nested_override(): + """Ensure proper scope naming of nested overrides.""" + # WARNING: Base name must match that used in `config.py`s `override()`. + base_name = 'overrides-' + + def _check_scopes(num_expected, debug_values): + scope_names = [s.name for s in spack.config.config.scopes.values()] + + for i in range(num_expected): + name = '{0}{1}'.format(base_name, i) + assert name in scope_names + + data = spack.config.config.get_config('config', name) + assert data['debug'] == debug_values[i] + + # Check results from single and nested override + with spack.config.override('config:debug', True): + with spack.config.override('config:debug', False): + _check_scopes(2, [True, False]) + + _check_scopes(1, [True]) + + +@pytest.mark.nomockstage +def test_alternate_override(monkeypatch): + """Ensure proper scope naming of override when conflict present.""" + # WARNING: Base name must match that used in `config.py`s `override()`. + base_name = 'overrides-' + + def _matching_scopes(regexpr): + return [spack.config.InternalConfigScope('{0}1'.format(base_name))] + + # Check that the alternate naming works + monkeypatch.setattr(spack.config.config, 'matching_scopes', + _matching_scopes) + + with spack.config.override('config:debug', False): + name = '{0}2'.format(base_name) + + scope_names = [s.name for s in spack.config.config.scopes.values() if + s.name.startswith(base_name)] + assert name in scope_names + + data = spack.config.config.get_config('config', name) + assert data['debug'] is False + + def test_immutable_scope(tmpdir): config_yaml = str(tmpdir.join('config.yaml')) with open(config_yaml, 'w') as f: -- cgit v1.2.3-70-g09d2