configuration: allow environment overrides

Add three new environment variables which can be used to override the
configuration file locations: WEST_CONFIG_SYSTEM, WEST_CONFIG_GLOBAL,
and WEST_CONFIG_LOCAL.

Use them in the tests. This also has the beneficial side effect of
making sure that "real" system settings do not interfere with the test
environment.

Signed-off-by: Marti Bolivar <marti.bolivar@nordicsemi.no>
This commit is contained in:
Marti Bolivar 2019-05-24 21:50:23 -06:00
parent b94b71bbfa
commit 6cf0a0236b
3 changed files with 66 additions and 16 deletions

View File

@ -37,6 +37,9 @@ Local files:
- Linux, macOS, Windows: <installation-root-directory>/.west/config
You can override these files' locations with the WEST_CONFIG_SYSTEM,
WEST_CONFIG_GLOBAL, and WEST_CONFIG_LOCAL environment variables.
Configuration values from later configuration files override configuration
from earlier ones. Local values have highest precedence, and system values
lowest.

View File

@ -27,6 +27,9 @@ Local files:
- Linux, macOS, Windows: ``<installation-root-directory>/.west/config``
You can override these files' locations with the ``WEST_CONFIG_SYSTEM``,
``WEST_CONFIG_GLOBAL``, and ``WEST_CONFIG_LOCAL`` environment variables.
Configuration values from later configuration files override configuration
from earlier ones. Local values have highest precedence, and system values
lowest.
@ -142,6 +145,9 @@ def _location(cfg):
if cfg == ConfigFile.ALL:
raise ValueError('ConfigFile.ALL has no location')
elif cfg == ConfigFile.SYSTEM:
if 'WEST_CONFIG_SYSTEM' in os.environ:
return os.environ['WEST_CONFIG_SYSTEM']
plat = platform.system()
if plat == 'Linux':
return '/etc/westconfig'
@ -154,14 +160,19 @@ def _location(cfg):
else:
raise ValueError('unsupported platform ' + plat)
elif cfg == ConfigFile.GLOBAL:
if platform.system() == 'Linux' and 'XDG_CONFIG_HOME' in env:
if 'WEST_CONFIG_GLOBAL' in os.environ:
return os.environ['WEST_CONFIG_GLOBAL']
elif platform.system() == 'Linux' and 'XDG_CONFIG_HOME' in env:
return os.path.join(env['XDG_CONFIG_HOME'], 'west', 'config')
else:
return canon_path(
os.path.join(os.path.expanduser('~'), '.westconfig'))
elif cfg == ConfigFile.LOCAL:
# Might raise WestNotFound!
return os.path.join(west_dir(), 'config')
if 'WEST_CONFIG_LOCAL' in os.environ:
return os.environ['WEST_CONFIG_LOCAL']
else:
# Might raise WestNotFound!
return os.path.join(west_dir(), 'config')
else:
raise ValueError('invalid configuration file {}'.format(cfg))

View File

@ -29,9 +29,8 @@ def tstloc(cfg):
# Monkeypatch for the config file location. assumes we are called
# in a tmpdir.
#
# Important: This is only safe to use to test the API in this process.
# DON'T call cmd('config ...'); subprocesses aren't patched,
# so the system location is the "real" one.
# Important: If you call cmd('config ...'), the subprocess isn't affected,
# so the location is determined by config_tmpdir().
if cfg == SYSTEM:
return os.path.join(os.getcwd(), 'system', 'config')
elif cfg == GLOBAL:
@ -52,6 +51,8 @@ def config_tmpdir(tmpdir):
# the temporary tox directories, for the same reason
# - ensure ~ exists (since the test environment
# doesn't let us run in the true user $HOME).
# - set WEST_CONFIG_SYSTEM to lie inside the tmpdir (to avoid
# interacting with the real system file)
# - set ZEPHYR_BASE (to avoid complaints in subcommand stderr)
#
# Using this makes the tests run faster than if we used
@ -75,11 +76,13 @@ def config_tmpdir(tmpdir):
# has a system file with a 'pytest' section in it) or the tests
# (if we're not setting ourselves up properly)
os.environ['ZEPHYR_BASE'] = str(tmpdir.join('no-zephyr-here'))
os.environ['WEST_CONFIG_SYSTEM'] = str(tmpdir.join('config.system'))
if 'pytest' in cfg():
del os.environ['ZEPHYR_BASE']
assert False, 'bad fixture setup'
yield tmpdir
del os.environ['ZEPHYR_BASE']
del os.environ['WEST_CONFIG_SYSTEM']
def test_config_global():
# Set a global config option via the command interface. Make sure
@ -148,8 +151,6 @@ def test_config_local():
@patch('west.configuration._location', new=tstloc)
def test_config_system():
# Basic test of system-level configuration.
#
# Since we use tstloc(), we can't call cmd('config ...') here.
config.update_config('pytest', 'key', 'val', configfile=SYSTEM)
assert cfg(f=ALL)['pytest']['key'] == 'val'
@ -163,8 +164,7 @@ def test_config_system():
@patch('west.configuration._location', new=tstloc)
def test_config_system_precedence():
# Test precedence rules, including system level.
#
# Since we use tstloc(), we can't call cmd('config ...') here.
config.update_config('pytest', 'key', 'sys', configfile=SYSTEM)
assert cfg(f=SYSTEM)['pytest']['key'] == 'sys'
assert cfg(f=ALL)['pytest']['key'] == 'sys'
@ -184,8 +184,7 @@ def test_config_system_precedence():
def test_system_creation():
# Test that the system file -- and just that file -- is created on
# demand.
#
# Since we use tstloc(), we can't call cmd('config ...') here.
assert not os.path.isfile(tstloc(SYSTEM))
assert not os.path.isfile(tstloc(GLOBAL))
assert not os.path.isfile(tstloc(LOCAL))
@ -203,8 +202,7 @@ def test_system_creation():
@patch('west.configuration._location', new=tstloc)
def test_global_creation():
# Like test_system_creation, for global config options.
#
# Since we use tstloc(), we can't call cmd('config ...') here.
assert not os.path.isfile(tstloc(SYSTEM))
assert not os.path.isfile(tstloc(GLOBAL))
assert not os.path.isfile(tstloc(LOCAL))
@ -222,8 +220,7 @@ def test_global_creation():
@patch('west.configuration._location', new=tstloc)
def test_local_creation():
# Like test_system_creation, for local config options.
#
# Since we use tstloc(), we can't call cmd('config ...') here.
assert not os.path.isfile(tstloc(SYSTEM))
assert not os.path.isfile(tstloc(GLOBAL))
assert not os.path.isfile(tstloc(LOCAL))
@ -308,3 +305,42 @@ def test_list():
assert sorted_list('--global') == ['pytest.baz=where']
assert sorted_list('--local') == ['pytest.bar=what',
'pytest.foo=who']
def test_env_overrides():
# Test that the WEST_CONFIG_SYSTEM etc. overrides work as
# expected.
#
# We are *not* using tstloc() and want to call cmd(), but
# we don't want to set the global or local environment variables
# in os.environ, or we'd have to be careful to clean them up,
# since they would affect other test cases. Use a copy instead.
# (Our autouse fixture cleans up the system variable.)
# Our test fixture already set up a system variable; test it.
assert not os.path.isfile(os.environ['WEST_CONFIG_SYSTEM'])
cmd('config --system pytest.foo bar')
assert os.path.isfile(os.environ['WEST_CONFIG_SYSTEM'])
assert cfg(f=SYSTEM)['pytest']['foo'] == 'bar'
# Copy the environment to make sure global and local settings
# take effect there, and only there.
env = os.environ.copy()
env['WEST_CONFIG_GLOBAL'] = os.path.abspath('config.global')
env['WEST_CONFIG_LOCAL'] = os.path.abspath('config.local')
config.update_config('pytest', 'foo', 'global-not-in-env',
configfile=GLOBAL)
assert not os.path.isfile(env['WEST_CONFIG_GLOBAL'])
assert cfg(f=ALL)['pytest']['foo'] == 'global-not-in-env'
cmd('config --global pytest.foo global-in-env', env=env)
assert os.path.isfile(env['WEST_CONFIG_GLOBAL'])
assert cmd('config pytest.foo', env=env).rstrip() == 'global-in-env'
config.update_config('pytest', 'foo', 'local-not-in-env',
configfile=LOCAL)
assert not os.path.isfile(env['WEST_CONFIG_LOCAL'])
assert cfg(f=ALL)['pytest']['foo'] == 'local-not-in-env'
cmd('config --local pytest.foo local-in-env', env=env)
assert os.path.isfile(env['WEST_CONFIG_LOCAL'])
assert cmd('config pytest.foo', env=env).rstrip() == 'local-in-env'