Skip to content

Commit 046f64c

Browse files
thorsten-kleinpdgendt
authored andcommitted
add tests for multiple west config files
Verify that multiple config files can be specified in the according environment variable (WEST_CONFIG_SYSTEM, WEST_CONFIG_GLOBAL, WEST_CONFIG_LOCAL) for each config level.
1 parent eedba99 commit 046f64c

File tree

1 file changed

+150
-1
lines changed

1 file changed

+150
-1
lines changed

tests/test_config.py

Lines changed: 150 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@
99
from typing import Any
1010

1111
import pytest
12-
from conftest import chdir, cmd, cmd_raises, update_env
12+
from conftest import WINDOWS, chdir, cmd, cmd_raises, tmp_west_topdir, update_env
1313

1414
from west import configuration as config
15+
from west.configuration import MalformedConfig
1516
from west.util import PathType, WestNotFound
1617

1718
SYSTEM = config.ConfigFile.SYSTEM
@@ -619,6 +620,154 @@ def test_config_precedence():
619620
assert cfg(f=LOCAL)['pytest']['precedence'] == 'local'
620621

621622

623+
def test_config_multiple(config_tmpdir):
624+
# Verify that local settings take precedence over global ones,
625+
# but that both values are still available, and that setting
626+
# either doesn't affect system settings.
627+
def write_config(config_file, section, key1, value1, key2, value2):
628+
config_file.parent.mkdir(exist_ok=True)
629+
630+
content = textwrap.dedent(f'''
631+
[{section}]
632+
{key1} = {value1}
633+
{key2} = {value2}
634+
''')
635+
636+
with open(config_file, 'w') as conf:
637+
conf.write(content)
638+
639+
# helper function to assert multiple config values
640+
def run_and_assert(expected_values: dict[str, dict[str, str]]):
641+
for scope, meta in expected_values.items():
642+
for flags, expected in meta.items():
643+
stdout = cmd(f'config --{scope} {flags}').rstrip()
644+
if type(expected) is list:
645+
stdout = stdout.splitlines()
646+
assert stdout == expected, f"{scope} {flags}: {expected} =! {stdout}"
647+
648+
# config file paths
649+
config_dir = pathlib.Path(config_tmpdir) / 'configs'
650+
config_s1 = config_dir / 'system 1'
651+
config_s2 = config_dir / 'system 2'
652+
config_g1 = config_dir / 'global 1'
653+
config_g2 = config_dir / 'global 2'
654+
config_l1 = config_dir / 'local 1'
655+
config_l2 = config_dir / 'local 2'
656+
657+
# create some configs with
658+
# - some individual option per config file (s1/s2/g1/g2/l1/l2))
659+
# - the same option (s/g/l) defined in multiple configs
660+
write_config(config_s1, 'sec', 's', '1 !"$&/()=?', 's1', '1 !"$&/()=?')
661+
write_config(config_s2, 'sec', 's', '2', 's2', '2')
662+
write_config(config_g1, 'sec', 'g', '1', 'g1', '1')
663+
write_config(config_g2, 'sec', 'g', '2', 'g2', '2')
664+
write_config(config_l1, 'sec', 'l', '1', 'l1', '1')
665+
write_config(config_l2, 'sec', 'l', '2', 'l2', '2')
666+
667+
# config file without read permission (does not work on Windows)
668+
if not WINDOWS:
669+
config_non_readable = config_dir / 'non-readable'
670+
config_non_readable.touch()
671+
config_non_readable.chmod(0o000)
672+
with update_env({'WEST_CONFIG_GLOBAL': f'{config_g1}{os.pathsep}{config_non_readable}'}):
673+
_, stderr = cmd_raises('config --global some.section', MalformedConfig)
674+
expected = f"Error while reading one of '{[str(config_g1), str(config_non_readable)]}'"
675+
assert expected in stderr
676+
677+
# specify multiple configs for each config level (separated by os.pathsep)
678+
os.environ["WEST_CONFIG_GLOBAL"] = f'{config_g1}{os.pathsep}{config_g2}'
679+
os.environ["WEST_CONFIG_SYSTEM"] = f'{config_s1}{os.pathsep}{config_s2}'
680+
os.environ["WEST_CONFIG_LOCAL"] = f'{config_l1}{os.pathsep}{config_l2}'
681+
682+
# check options from individual files and that options from latter configs override
683+
expected = {
684+
'system': {'sec.s1': '1 !"$&/()=?', 'sec.s2': '2', 'sec.s': '2'},
685+
'global': {'sec.g1': '1', 'sec.g2': '2', 'sec.g': '2'},
686+
'local': {'sec.l1': '1', 'sec.l2': '2', 'sec.l': '2'},
687+
}
688+
run_and_assert(expected)
689+
690+
# check that list-paths gives correct output
691+
expected = {
692+
'system': {'--list-paths': [str(config_s1), str(config_s2)]},
693+
'global': {'--list-paths': [str(config_g1), str(config_g2)]},
694+
'local': {'--list-paths': [str(config_l1), str(config_l2)]},
695+
}
696+
run_and_assert(expected)
697+
698+
# writing not possible if multiple configs are used
699+
_, stderr = cmd_raises('config --local sec.l3 3', ValueError)
700+
assert f'Cannot set value if multiple configs in use: {[config_l1, config_l2]}' in stderr
701+
702+
703+
@pytest.mark.parametrize("location", [LOCAL, GLOBAL, SYSTEM])
704+
def test_config_multiple_write(location):
705+
# write to a config with a single config file must work, even if other
706+
# locations have multiple configs in use
707+
flag = west_flag[location]
708+
env_var = west_env[location]
709+
710+
configs_dir = pathlib.Path("configs")
711+
config1 = (configs_dir / 'config 1').resolve()
712+
config2 = (configs_dir / 'config 2').resolve()
713+
config3 = (configs_dir / 'config 3').resolve()
714+
715+
env = {west_env[location]: f'{config1}'}
716+
other_locations = [c for c in [LOCAL, GLOBAL, SYSTEM] if c != location]
717+
for loc in other_locations:
718+
env[west_env[loc]] = f'{config2}{os.pathsep}{config3}'
719+
720+
with update_env(env):
721+
cmd(f'config {flag} key.value {env_var}')
722+
stdout = cmd(f'config {flag} key.value')
723+
assert [env_var] == stdout.rstrip().splitlines()
724+
725+
726+
@pytest.mark.parametrize("location", [LOCAL, GLOBAL, SYSTEM])
727+
def test_config_multiple_relative(location):
728+
# specify multiple configs for each config level (separated by os.pathsep).
729+
# The paths may be relative relative paths, which are always anchored to
730+
# west topdir. For the test, the cwd is changed to another cwd to ensure
731+
# that relative paths are anchored correctly.
732+
flag = west_flag[location]
733+
env_var = west_env[location]
734+
735+
msg = "'{file}' is relative but 'west topdir' is not defined"
736+
737+
# create some configs
738+
configs_dir = pathlib.Path('config')
739+
configs_dir.mkdir()
740+
config1 = (configs_dir / 'config 1').resolve()
741+
config2 = (configs_dir / 'config 2').resolve()
742+
config1.touch()
743+
config2.touch()
744+
745+
west_topdir = pathlib.Path.cwd()
746+
cwd = west_topdir / 'any' / 'other cwd'
747+
cwd.mkdir(parents=True)
748+
with chdir(cwd):
749+
config2_rel = config2.relative_to(west_topdir)
750+
command = f'config {flag} --list-paths'
751+
env_value = f'{config1}{os.pathsep}{config2_rel}'
752+
with update_env({env_var: env_value}):
753+
# cannot anchor relative path if no west topdir exists
754+
exc, _ = cmd_raises(command, WestNotFound)
755+
assert msg.format(file=config2_rel) in str(exc.value)
756+
757+
# relative paths are anchored to west topdir
758+
with tmp_west_topdir(west_topdir):
759+
stdout = cmd(command)
760+
assert [str(config1), str(config2)] == stdout.rstrip().splitlines()
761+
762+
# if a wrong separator is used, no config file must be found
763+
wrong_sep = ':' if WINDOWS else ';'
764+
env_value = f'{config1}{wrong_sep}{config2_rel}'
765+
with update_env({env_var: env_value}):
766+
# no path is listed
767+
stdout = cmd(command)
768+
assert not stdout
769+
770+
622771
def test_config_missing_key():
623772
_, err_msg = cmd_raises('config pytest', SystemExit)
624773
assert 'invalid configuration option "pytest"; expected "section.key" format' in err_msg

0 commit comments

Comments
 (0)