Add support for nested "overrides" scopes.

This commit is contained in:
Tamara Dahlgren 2019-09-17 19:27:33 -07:00 committed by Tamara Dahlgren
parent b07460ab5f
commit 87cdfa2c25
2 changed files with 72 additions and 2 deletions

View file

@ -32,6 +32,7 @@
import copy
import os
import re
import sys
import multiprocessing
from contextlib import contextmanager
@ -354,6 +355,15 @@ def highest_precedence_scope(self):
"""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

View file

@ -627,6 +627,54 @@ def test_add_command_line_scopes(tmpdir, mutable_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: